mirror of
https://github.com/iconify/iconify.git
synced 2024-12-22 01:38:56 +00:00
Move version 2 to a big monorepo
This commit is contained in:
commit
58d4cf3d49
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
node_modules
|
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"quoteProps": "consistent",
|
||||
"endOfLine": "lf"
|
||||
}
|
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"spellright.language": [
|
||||
"en"
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"markdown",
|
||||
"latex",
|
||||
"plaintext"
|
||||
]
|
||||
}
|
69
README.md
Normal file
69
README.md
Normal 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
7
lerna.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": "independent",
|
||||
"npmClient": "npm",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
}
|
6590
package-lock.json
generated
Normal file
6590
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal 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
0
packages/.gitignore
vendored
Normal file
4
packages/browser-tests/.gitignore
vendored
Normal file
4
packages/browser-tests/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
lib
|
||||
dist
|
97
packages/browser-tests/build.js
Normal file
97
packages/browser-tests/build.js
Normal 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
1139
packages/browser-tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
packages/browser-tests/package.json
Normal file
35
packages/browser-tests/package.json
Normal 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": {}
|
||||
}
|
54
packages/browser-tests/rollup.config.js
Normal file
54
packages/browser-tests/rollup.config.js
Normal 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;
|
199
packages/browser-tests/tests/10-fake-api-test.ts
Normal file
199
packages/browser-tests/tests/10-fake-api-test.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
344
packages/browser-tests/tests/10-finder-iconify-test.ts
Normal file
344
packages/browser-tests/tests/10-finder-iconify-test.ts
Normal 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);
|
||||
});
|
||||
});
|
74
packages/browser-tests/tests/10-finder-v1-test.ts
Normal file
74
packages/browser-tests/tests/10-finder-v1-test.ts
Normal 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);
|
||||
});
|
||||
});
|
74
packages/browser-tests/tests/10-finder-v2-test.ts
Normal file
74
packages/browser-tests/tests/10-finder-v2-test.ts
Normal 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);
|
||||
});
|
||||
});
|
219
packages/browser-tests/tests/10-node-attributes-test.ts
Normal file
219
packages/browser-tests/tests/10-node-attributes-test.ts
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
40
packages/browser-tests/tests/10-observer-creation-test.ts
Normal file
40
packages/browser-tests/tests/10-observer-creation-test.ts
Normal 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>';
|
||||
});
|
||||
});
|
110
packages/browser-tests/tests/10-observer-manipulation-test.ts
Normal file
110
packages/browser-tests/tests/10-observer-manipulation-test.ts
Normal 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>';
|
||||
});
|
||||
});
|
991
packages/browser-tests/tests/20-renderer-v1-test.ts
Normal file
991
packages/browser-tests/tests/20-renderer-v1-test.ts
Normal 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="<Cash>!"></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);
|
||||
expect(items[2].finder).to.be.equal(iconifyIconFinder);
|
||||
expect(items[3].finder).to.be.equal(iconifyIconFinder);
|
||||
expect(items[4].finder).to.be.equal(iconifyFinder);
|
||||
expect(items[5].finder).to.be.equal(iconifyFinder);
|
||||
|
||||
/**
|
||||
* Test third icon (first 2 should be last)
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account-cash',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi-account-cash'); // name should stay as is
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
expect(svg.getAttribute('title')).to.be.equal('<Cash>!'); // title, unescaped
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi-account-cash');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test fourth item
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
vFlip: true,
|
||||
width: 'auto',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-icon'
|
||||
);
|
||||
|
||||
// Block
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test fifth icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account-box',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 2,
|
||||
width: '42',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('42');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account-box');
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
|
||||
// IE rounds value
|
||||
let verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account-box');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Save SVG for rotation test below
|
||||
const rotationSVG = svg;
|
||||
|
||||
/**
|
||||
* Test sixth icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-icon should-be-block'
|
||||
);
|
||||
|
||||
// Block
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test first icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
expect(svg.style.color).to.be.equal('red'); // color from inline style
|
||||
expect(svg.style.borderWidth).to.be.equal('1px'); // border from inline style
|
||||
|
||||
// IE rounds value
|
||||
verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test second item
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Set style
|
||||
element.element.style.border = '1px solid green';
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi test-icon'
|
||||
); // add 'test-icon' class, remove 'iconify--mdi-account'
|
||||
expect(svg.style.verticalAlign).to.be.equal('0px'); // inline style overrides verticalAlign from data-align attribute
|
||||
expect(svg.style.borderWidth).to.be.equal('1px'); // style set via DOM
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// All placeholder should have been replaced
|
||||
expect(items).to.be.eql([]);
|
||||
expect(findPlaceholders(node)).to.be.eql([]);
|
||||
|
||||
/**
|
||||
* Test finding modified SVG
|
||||
*/
|
||||
// Remove rotation
|
||||
rotationSVG.removeAttribute('data-rotate');
|
||||
const items2 = findPlaceholders(node);
|
||||
expect(items2.length).to.be.equal(1);
|
||||
|
||||
element = items2.shift();
|
||||
expect(element.element).to.be.equal(rotationSVG);
|
||||
expect(element.customisations).to.be.eql({
|
||||
inline: true,
|
||||
width: '42',
|
||||
});
|
||||
});
|
||||
|
||||
it('Change attributes', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing attributes v1: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
expect(svg.style.color).to.be.equal('red'); // color from inline style
|
||||
|
||||
// IE rounds value
|
||||
let verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Render SVG without changes
|
||||
*/
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql(lastCustomisations); // customisations were not changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test attributes, compare them with last SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
[
|
||||
'aria-hidden',
|
||||
'focusable',
|
||||
'role',
|
||||
'width',
|
||||
'height',
|
||||
'viewBox',
|
||||
'preserveAspectRatio',
|
||||
'data-icon',
|
||||
'class',
|
||||
].forEach((attr) => {
|
||||
expect(svg.getAttribute(attr)).to.be.equal(
|
||||
lastSVG.getAttribute(attr),
|
||||
'Different values for attribute ' + attr
|
||||
);
|
||||
});
|
||||
|
||||
['vertical-align', 'color'].forEach((attr) => {
|
||||
expect(svg.style[attr]).to.be.equal(
|
||||
lastSVG.style[attr],
|
||||
'Different values for style ' + attr
|
||||
);
|
||||
});
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
lastElement = element;
|
||||
lastCustomisations = customisations;
|
||||
lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Rotate icon
|
||||
*/
|
||||
lastSVG.setAttribute('data-rotate', '1');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test changed attributes
|
||||
expect(svg.getAttribute('data-rotate')).to.be.equal('1');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
lastElement = element;
|
||||
lastCustomisations = customisations;
|
||||
lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Change icon name and reset flip
|
||||
*/
|
||||
lastSVG.setAttribute('data-icon', 'mdi-account');
|
||||
lastSVG.removeAttribute('data-flip');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
|
||||
expect(element.name).to.not.be.eql(lastElement.name);
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 1,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test changed attributes
|
||||
expect(svg.getAttribute('data-rotate')).to.be.equal('1');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi-account');
|
||||
expect(svg.getAttribute('data-flip')).to.be.equal(null);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi-account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Invalid icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing invalid icon name v1: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
/**
|
||||
* Change icon name to invalid
|
||||
*/
|
||||
svg.setAttribute('data-icon', 'mdi');
|
||||
const name = element.finder.name(svg);
|
||||
expect(name).to.be.equal('mdi');
|
||||
expect(stringToIcon(name as string)).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Empty icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing empty icon name v1: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
/**
|
||||
* Change icon name to invalid
|
||||
*/
|
||||
svg.removeAttribute('data-icon');
|
||||
expect(element.finder.name(svg)).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Change icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing icon name v1: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
expect(iconData.body.indexOf('M6 17c0-2')).to.be.equal(
|
||||
-1,
|
||||
'Wrong icon body: ' + iconData.body
|
||||
);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Change name
|
||||
*/
|
||||
svg.setAttribute('data-icon', 'mdi:account-box');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
|
||||
expect(element.name).to.not.be.eql(lastElement.name); // different 'name' property
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql(lastCustomisations); // customisations were not changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Test icon body to make sure icon was changed
|
||||
expect(iconData.body.indexOf('M6 17c0-2')).to.not.be.equal(
|
||||
-1,
|
||||
'Wrong icon body: ' + iconData.body
|
||||
);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account-box');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Rotating icon', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing rotation v1: <span class="iconify-icon" data-icon="mdi:home"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
if (svg.innerHTML) {
|
||||
const html = svg.innerHTML;
|
||||
// Test icon body to make sure icon has no transformation
|
||||
expect(html.indexOf('transform="')).to.be.equal(
|
||||
-1,
|
||||
'Found transform in icon: ' + html
|
||||
);
|
||||
}
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Rotate
|
||||
*/
|
||||
svg.setAttribute('data-rotate', '2');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
rotate: 2,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
if (svg.innerHTML) {
|
||||
const html = svg.innerHTML;
|
||||
// Test icon body to make sure icon was changed
|
||||
expect(html.indexOf('transform="')).to.not.be.equal(
|
||||
-1,
|
||||
'Missing transform in icon: ' + html
|
||||
);
|
||||
}
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Changing size', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing size v1: <span class="iconify" data-icon="mdi:home" style="box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Check dimensions
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Set height
|
||||
*/
|
||||
svg.setAttribute('data-height', '24');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
height: '24',
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Check dimensions
|
||||
expect(svg.getAttribute('width')).to.be.equal('24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Changing alignment', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing alignment v1: <span class="iconify" data-icon="mdi:home" data-width="48" data-height="24" style="box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
width: '48',
|
||||
height: '24',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Check dimensions and alignment
|
||||
expect(svg.getAttribute('width')).to.be.equal('48');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('preserveAspectRatio')).to.be.equal(
|
||||
'xMidYMid meet'
|
||||
);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Set alignment
|
||||
*/
|
||||
svg.setAttribute('data-align', 'left, bottom');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
width: '48',
|
||||
height: '24',
|
||||
hAlign: 'left',
|
||||
vAlign: 'bottom',
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Check dimensions and alignment
|
||||
expect(svg.getAttribute('width')).to.be.equal('48');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('preserveAspectRatio')).to.be.equal(
|
||||
'xMinYMax meet'
|
||||
);
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
});
|
995
packages/browser-tests/tests/20-renderer-v2-test.ts
Normal file
995
packages/browser-tests/tests/20-renderer-v2-test.ts
Normal file
@ -0,0 +1,995 @@
|
||||
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';
|
||||
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-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 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 v2</p><ul>' +
|
||||
'<li>Inline icons:<br />' +
|
||||
' Red icon with red border: <span class="iconify-inline" data-icon="mdi:home" style="color: red; border: 1px solid red;"></span><br />' +
|
||||
' No vertical-align, green border: <i class="iconify test-icon iconify-inline 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="<Cash>!"></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);
|
||||
expect(items[2].finder).to.be.equal(iconifyIconFinder);
|
||||
expect(items[3].finder).to.be.equal(iconifyIconFinder);
|
||||
expect(items[4].finder).to.be.equal(iconifyFinder);
|
||||
expect(items[5].finder).to.be.equal(iconifyFinder);
|
||||
|
||||
/**
|
||||
* Test third icon (first 2 should be last)
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account-cash',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi-account-cash'); // name should stay as is
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
expect(svg.getAttribute('title')).to.be.equal('<Cash>!'); // title, unescaped
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi-account-cash');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test fourth item
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
vFlip: true,
|
||||
width: 'auto',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-icon'
|
||||
);
|
||||
|
||||
// Block
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test fifth icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account-box',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 2,
|
||||
width: '42',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('42');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account-box');
|
||||
expect(svg.getAttribute('class')).to.be.equal('iconify iconify--mdi');
|
||||
|
||||
// IE rounds value
|
||||
let verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account-box');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Save SVG for rotation test below
|
||||
const rotationSVG = svg;
|
||||
|
||||
/**
|
||||
* Test sixth icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-icon should-be-block'
|
||||
);
|
||||
|
||||
// Block
|
||||
expect(svg.style.verticalAlign).to.be.equal('');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test first icon
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-inline'
|
||||
);
|
||||
expect(svg.style.color).to.be.equal('red'); // color from inline style
|
||||
expect(svg.style.borderWidth).to.be.equal('1px'); // border from inline style
|
||||
|
||||
// IE rounds value
|
||||
verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
/**
|
||||
* Test second item
|
||||
*/
|
||||
element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Set style
|
||||
element.element.style.border = '1px solid green';
|
||||
|
||||
// Get icon data
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:account');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi test-icon iconify-inline'
|
||||
); // add 'test-icon' class, remove 'iconify--mdi-account'
|
||||
expect(svg.style.verticalAlign).to.be.equal('0px'); // inline style overrides verticalAlign from data-align attribute
|
||||
expect(svg.style.borderWidth).to.be.equal('1px'); // style set via DOM
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// All placeholder should have been replaced
|
||||
expect(items).to.be.eql([]);
|
||||
expect(findPlaceholders(node)).to.be.eql([]);
|
||||
|
||||
/**
|
||||
* Test finding modified SVG
|
||||
*/
|
||||
// Remove rotation
|
||||
rotationSVG.removeAttribute('data-rotate');
|
||||
const items2 = findPlaceholders(node);
|
||||
expect(items2.length).to.be.equal(1);
|
||||
|
||||
element = items2.shift();
|
||||
expect(element.element).to.be.equal(rotationSVG);
|
||||
expect(element.customisations).to.be.eql({
|
||||
inline: true,
|
||||
width: '42',
|
||||
});
|
||||
});
|
||||
|
||||
it('Change attributes', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing attributes v2: <span class="iconify-inline" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Test SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
expect(svg.getAttribute('viewBox')).to.be.equal('0 0 24 24');
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi:home');
|
||||
expect(svg.getAttribute('class')).to.be.equal(
|
||||
'iconify iconify--mdi iconify-inline'
|
||||
);
|
||||
expect(svg.style.color).to.be.equal('red'); // color from inline style
|
||||
|
||||
// IE rounds value
|
||||
let verticalAlign = svg.style.verticalAlign;
|
||||
expect(
|
||||
verticalAlign === '-0.125em' || verticalAlign === '-0.12em'
|
||||
).to.be.equal(true, 'Invalid vertical-align value: ' + verticalAlign);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Render SVG without changes
|
||||
*/
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql(lastCustomisations); // customisations were not changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test attributes, compare them with last SVG
|
||||
expect(svg.tagName.toUpperCase()).to.be.equal('SVG');
|
||||
[
|
||||
'aria-hidden',
|
||||
'focusable',
|
||||
'role',
|
||||
'width',
|
||||
'height',
|
||||
'viewBox',
|
||||
'preserveAspectRatio',
|
||||
'data-icon',
|
||||
'class',
|
||||
].forEach((attr) => {
|
||||
expect(svg.getAttribute(attr)).to.be.equal(
|
||||
lastSVG.getAttribute(attr),
|
||||
'Different values for attribute ' + attr
|
||||
);
|
||||
});
|
||||
|
||||
['vertical-align', 'color'].forEach((attr) => {
|
||||
expect(svg.style[attr]).to.be.equal(
|
||||
lastSVG.style[attr],
|
||||
'Different values for style ' + attr
|
||||
);
|
||||
});
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
lastElement = element;
|
||||
lastCustomisations = customisations;
|
||||
lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Rotate icon
|
||||
*/
|
||||
lastSVG.setAttribute('data-rotate', '1');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test changed attributes
|
||||
expect(svg.getAttribute('data-rotate')).to.be.equal('1');
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
|
||||
// Copy variables for next test
|
||||
lastElement = element;
|
||||
lastCustomisations = customisations;
|
||||
lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Change icon name and reset flip
|
||||
*/
|
||||
lastSVG.setAttribute('data-icon', 'mdi-account');
|
||||
lastSVG.removeAttribute('data-flip');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
|
||||
expect(element.name).to.not.be.eql(lastElement.name);
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
});
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
rotate: 1,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test changed attributes
|
||||
expect(svg.getAttribute('data-rotate')).to.be.equal('1');
|
||||
expect(svg.getAttribute('data-icon')).to.be.equal('mdi-account');
|
||||
expect(svg.getAttribute('data-flip')).to.be.equal(null);
|
||||
|
||||
// Test finder on SVG
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi-account');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Invalid icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing invalid icon name v2: <span class="iconify-inline" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: true,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
/**
|
||||
* Change icon name to invalid
|
||||
*/
|
||||
svg.setAttribute('data-icon', 'mdi');
|
||||
const name = element.finder.name(svg);
|
||||
expect(name).to.be.equal('mdi');
|
||||
expect(stringToIcon(name as string)).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Empty icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing empty icon name v2: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
/**
|
||||
* Change icon name to invalid
|
||||
*/
|
||||
svg.removeAttribute('data-icon');
|
||||
expect(element.finder.name(svg)).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Change icon name', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing icon name v2: <span class="iconify" data-icon="mdi:home" data-flip="horizontal" style="color: red; box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
expect(iconData.body.indexOf('M6 17c0-2')).to.be.equal(
|
||||
-1,
|
||||
'Wrong icon body: ' + iconData.body
|
||||
);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Change name
|
||||
*/
|
||||
svg.setAttribute('data-icon', 'mdi:account-box');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
|
||||
expect(element.name).to.not.be.eql(lastElement.name); // different 'name' property
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql(lastCustomisations); // customisations were not changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
hFlip: true,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Test icon body to make sure icon was changed
|
||||
expect(iconData.body.indexOf('M6 17c0-2')).to.not.be.equal(
|
||||
-1,
|
||||
'Wrong icon body: ' + iconData.body
|
||||
);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:account-box');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Rotating icon', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing rotation v2: <span class="iconify-icon" data-icon="mdi:home"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyIconFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
if (svg.innerHTML) {
|
||||
const html = svg.innerHTML;
|
||||
// Test icon body to make sure icon has no transformation
|
||||
expect(html.indexOf('transform="')).to.be.equal(
|
||||
-1,
|
||||
'Found transform in icon: ' + html
|
||||
);
|
||||
}
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Rotate
|
||||
*/
|
||||
svg.setAttribute('data-rotate', '2');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
rotate: 2,
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
if (svg.innerHTML) {
|
||||
const html = svg.innerHTML;
|
||||
// Test icon body to make sure icon was changed
|
||||
expect(html.indexOf('transform="')).to.not.be.equal(
|
||||
-1,
|
||||
'Missing transform in icon: ' + html
|
||||
);
|
||||
}
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Changing size', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing size v2: <span class="iconify" data-icon="mdi:home" style="box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Check dimensions
|
||||
expect(svg.getAttribute('width')).to.be.equal('1em');
|
||||
expect(svg.getAttribute('height')).to.be.equal('1em');
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Set height
|
||||
*/
|
||||
svg.setAttribute('data-height', '24');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
height: '24',
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Check dimensions
|
||||
expect(svg.getAttribute('width')).to.be.equal('24');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
|
||||
it('Changing alignment', () => {
|
||||
const node = getNode('renderer');
|
||||
node.innerHTML =
|
||||
'<div>Testing alignment v2: <span class="iconify" data-icon="mdi:home" data-width="48" data-height="24" style="box-shadow: 0 0 2px black;"></span></div>';
|
||||
|
||||
// Get items
|
||||
const items = findPlaceholders(node);
|
||||
expect(items.length).to.be.equal(1);
|
||||
|
||||
/**
|
||||
* Test icon
|
||||
*/
|
||||
let element = items.shift();
|
||||
|
||||
// Test element
|
||||
expect(element.name).to.be.eql({
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
});
|
||||
expect(element.finder).to.be.equal(iconifyFinder);
|
||||
|
||||
// Get and test customisations
|
||||
let customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
width: '48',
|
||||
height: '24',
|
||||
});
|
||||
|
||||
// Get icon data
|
||||
let iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
// Render icon
|
||||
let svg = renderIcon(element, customisations, iconData);
|
||||
|
||||
// Check dimensions and alignment
|
||||
expect(svg.getAttribute('width')).to.be.equal('48');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('preserveAspectRatio')).to.be.equal(
|
||||
'xMidYMid meet'
|
||||
);
|
||||
|
||||
// Copy variables for next test
|
||||
let lastElement = element;
|
||||
let lastCustomisations = customisations;
|
||||
let lastSVG = svg;
|
||||
|
||||
/**
|
||||
* Set alignment
|
||||
*/
|
||||
svg.setAttribute('data-align', 'left, bottom');
|
||||
|
||||
// Create new element
|
||||
element = {
|
||||
element: svg,
|
||||
finder: element.finder,
|
||||
name: stringToIcon(element.finder.name(svg) as string),
|
||||
};
|
||||
expect(element).to.not.be.eql(lastElement); // different 'element' property
|
||||
expect(element.name).to.be.eql(lastElement.name);
|
||||
|
||||
// Get customisations
|
||||
customisations = element.finder.customisations(element.element);
|
||||
expect(customisations).to.not.be.eql(lastCustomisations); // customisations were changed
|
||||
expect(customisations).to.be.eql({
|
||||
inline: false,
|
||||
width: '48',
|
||||
height: '24',
|
||||
hAlign: 'left',
|
||||
vAlign: 'bottom',
|
||||
});
|
||||
|
||||
// Get icon data and render SVG
|
||||
iconData = getIcon(storage, element.name.name);
|
||||
expect(iconData).to.not.be.equal(null);
|
||||
|
||||
svg = renderIcon(element, customisations, iconData);
|
||||
expect(svg).to.not.be.eql(lastSVG);
|
||||
|
||||
// Check dimensions and alignment
|
||||
expect(svg.getAttribute('width')).to.be.equal('48');
|
||||
expect(svg.getAttribute('height')).to.be.equal('24');
|
||||
expect(svg.getAttribute('preserveAspectRatio')).to.be.equal(
|
||||
'xMinYMax meet'
|
||||
);
|
||||
|
||||
// Test finder
|
||||
expect(element.finder.name(svg)).to.be.equal('mdi:home');
|
||||
expect(element.finder.customisations(svg)).to.be.eql(customisations);
|
||||
});
|
||||
});
|
66
packages/browser-tests/tests/20-scan-dom-test.ts
Normal file
66
packages/browser-tests/tests/20-scan-dom-test.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import { addFinder } from '@iconify/iconify/lib/finder';
|
||||
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
|
||||
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
|
||||
import { getStorage, addIconSet } from '@iconify/core/lib/storage';
|
||||
import { browserModules } from '@iconify/iconify/lib/modules';
|
||||
import { scanDOM } from '@iconify/iconify/lib/scan';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
|
||||
describe('Scanning DOM', () => {
|
||||
// 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('Scan DOM with preloaded icons', () => {
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM</p><ul>' +
|
||||
'<li>Valid icons:' +
|
||||
' <span class="iconify" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <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 icon:' +
|
||||
' <iconify-icon data-icon="mdi-account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <iconify-icon data-icon="mdi:account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
browserModules.root = node;
|
||||
scanDOM();
|
||||
|
||||
// Find elements
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(4);
|
||||
});
|
||||
});
|
373
packages/browser-tests/tests/21-scan-dom-api-test.ts
Normal file
373
packages/browser-tests/tests/21-scan-dom-api-test.ts
Normal file
@ -0,0 +1,373 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import { addFinder } from '@iconify/iconify/lib/finder';
|
||||
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';
|
||||
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
|
||||
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
|
||||
import { browserModules } from '@iconify/iconify/lib/modules';
|
||||
import { scanDOM } from '@iconify/iconify/lib/scan';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
|
||||
// Set API
|
||||
setAPIModule({
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
});
|
||||
coreModules.api = API;
|
||||
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
return 'scan-dom-api-' + prefixCounter++;
|
||||
}
|
||||
|
||||
describe('Scanning DOM with API', () => {
|
||||
it('Scan DOM with API', (done) => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
},
|
||||
[prefix1, prefix2]
|
||||
);
|
||||
|
||||
// Set icons, load them with various delay
|
||||
const data1: FakeData = {
|
||||
icons: ['home', 'account-cash'],
|
||||
delay: 100,
|
||||
data: {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
'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"/>',
|
||||
},
|
||||
'home': {
|
||||
body:
|
||||
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix1, data1);
|
||||
|
||||
const data2: FakeData = {
|
||||
icons: ['account', 'account-box'],
|
||||
delay: 500,
|
||||
data: {
|
||||
prefix: prefix2,
|
||||
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': {
|
||||
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,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix2, data2);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API</p><ul>' +
|
||||
'<li>Inline icons:' +
|
||||
' <span class="iconify iconify-inline" data-icon="' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>Block icons:' +
|
||||
' <iconify-icon data-icon="' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <i class="iconify-icon" data-icon="' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></i>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
browserModules.root = node;
|
||||
|
||||
scanDOM();
|
||||
|
||||
// First API response should have loaded
|
||||
setTimeout(() => {
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(
|
||||
2,
|
||||
'Expected to find 2 rendered SVG elements'
|
||||
);
|
||||
}, 200);
|
||||
|
||||
// Second API response should have loaded
|
||||
setTimeout(() => {
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(
|
||||
4,
|
||||
'Expected to find 4 rendered SVG elements'
|
||||
);
|
||||
done();
|
||||
}, 700);
|
||||
});
|
||||
|
||||
it('Changing icon name before it loaded', (done) => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
},
|
||||
[prefix1, prefix2]
|
||||
);
|
||||
|
||||
// Set icons, load them with various delay
|
||||
const data1: FakeData = {
|
||||
icons: ['home', 'account-cash'],
|
||||
delay: 100,
|
||||
data: {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
'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"/>',
|
||||
},
|
||||
'home': {
|
||||
body:
|
||||
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix1, data1);
|
||||
|
||||
const data2: FakeData = {
|
||||
icons: ['account', 'account-box'],
|
||||
delay: 500,
|
||||
data: {
|
||||
prefix: prefix2,
|
||||
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': {
|
||||
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,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix2, data2);
|
||||
|
||||
const data1b: FakeData = {
|
||||
icons: ['account', 'account-box'],
|
||||
delay: 800, // +100ms for first query
|
||||
data: {
|
||||
prefix: prefix1,
|
||||
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': {
|
||||
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,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix1, data1b);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API: renamed icon</p><ul>' +
|
||||
'<li>Default finder:' +
|
||||
' <span class="iconify-inline first-icon" data-icon="' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify-inline second-icon iconify--mdi-account" data-icon="' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>IconifyIcon finder:' +
|
||||
' <iconify-icon class="third-icon" data-icon="' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <iconify-icon class="fourth-icon" data-icon="' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
browserModules.root = node;
|
||||
|
||||
scanDOM();
|
||||
|
||||
// Make sure no icons were rendered yet
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(
|
||||
0,
|
||||
'Expected to find 0 rendered SVG elements'
|
||||
);
|
||||
|
||||
// Change icon name
|
||||
const icon = node.querySelector('iconify-icon[title]');
|
||||
expect(icon).to.not.be.equal(null);
|
||||
expect(icon.getAttribute('class')).to.be.equal('third-icon');
|
||||
icon.setAttribute('data-icon', prefix1 + ':account');
|
||||
|
||||
// First API response should have loaded, but only 1 icon should have been rendered
|
||||
setTimeout(() => {
|
||||
// Loaded for prefix1: account-cash, home
|
||||
// Loaded for prefix2: -
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1, '200ms delay error');
|
||||
}, 200);
|
||||
|
||||
// Second API response should have loaded
|
||||
setTimeout(() => {
|
||||
// Loaded for prefix1: account-cash, home
|
||||
// Loaded for prefix2: account, account-box
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(3, '700ms delay error');
|
||||
}, 700);
|
||||
|
||||
// Renamed icon from first API response
|
||||
setTimeout(() => {
|
||||
// Loaded for prefix1: account-cash, home, account-box, account
|
||||
// Loaded for prefix2: account, account-box
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(4, '1000ms delay error');
|
||||
done();
|
||||
}, 1100);
|
||||
});
|
||||
|
||||
it('Changing icon name before it loaded to invalid name', (done) => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
},
|
||||
[prefix1, prefix2]
|
||||
);
|
||||
|
||||
// Set icons, load them with various delay
|
||||
const data1: FakeData = {
|
||||
icons: ['home', 'account-cash'],
|
||||
delay: 100,
|
||||
data: {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
'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"/>',
|
||||
},
|
||||
'home': {
|
||||
body:
|
||||
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix1, data1);
|
||||
|
||||
const data2: FakeData = {
|
||||
icons: ['account', 'account-box'],
|
||||
delay: 500,
|
||||
data: {
|
||||
prefix: prefix2,
|
||||
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': {
|
||||
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,
|
||||
},
|
||||
};
|
||||
setFakeData(prefix2, data2);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API: invalid name</p><ul>' +
|
||||
'<li>Inline icons:' +
|
||||
' <span class="iconify" data-icon="' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify test-icon iconify--mdi-account" data-icon="' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>Block icons:' +
|
||||
' <iconify-icon data-icon="' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <iconify-icon data-icon="' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
browserModules.root = node;
|
||||
|
||||
scanDOM();
|
||||
|
||||
// Change icon name
|
||||
const icon = node.querySelector('iconify-icon[title]');
|
||||
expect(icon).to.not.be.equal(null);
|
||||
icon.setAttribute('data-icon', 'foo');
|
||||
|
||||
// First API response should have loaded, but only 1 icon
|
||||
setTimeout(() => {
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
}, 200);
|
||||
|
||||
// Second API response should have loaded
|
||||
setTimeout(() => {
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(3);
|
||||
done();
|
||||
}, 700);
|
||||
});
|
||||
});
|
36
packages/browser-tests/tests/30-iconify-api-test.ts
Normal file
36
packages/browser-tests/tests/30-iconify-api-test.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import Iconify from '@iconify/iconify/lib/iconify';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
const selector =
|
||||
'span.iconify, i.iconify, span.iconify-inline, i.iconify-inline';
|
||||
|
||||
const node = getNode('iconify-api');
|
||||
Iconify.setRoot(node);
|
||||
|
||||
node.innerHTML =
|
||||
'<div><p>Testing Iconify with API</p><ul>' +
|
||||
'<li>Inline icons:' +
|
||||
' <span class="iconify-inline" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify iconify-inline test-icon iconify--mdi-account" data-icon="mdi:account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>Block icons:' +
|
||||
' <i class="iconify" data-icon="mdi:account-cash" title="<Cash>!"></i>' +
|
||||
' <span class="iconify" data-icon="mdi:account-box" data-inline="true" data-rotate="2" data-width="42"></span>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
describe('Testing Iconify object', () => {
|
||||
it('Rendering icons with API', () => {
|
||||
// Icons should have been replaced by now
|
||||
let list = node.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(0);
|
||||
|
||||
list = node.querySelectorAll('svg.iconify');
|
||||
expect(list.length).to.be.equal(4);
|
||||
});
|
||||
});
|
104
packages/browser-tests/tests/30-iconify-basic-test.ts
Normal file
104
packages/browser-tests/tests/30-iconify-basic-test.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import Iconify from '@iconify/iconify/lib/iconify';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
const selector =
|
||||
'span.iconify, i.iconify, span.iconify-inline, i.iconify-inline';
|
||||
|
||||
const node1 = getNode('iconify-basic');
|
||||
const node2 = getNode('iconify-basic');
|
||||
|
||||
// Set root node
|
||||
Iconify.setRoot(node1);
|
||||
|
||||
describe('Testing Iconify object', () => {
|
||||
const prefix = 'invalid-' + Date.now();
|
||||
|
||||
// Add mentioned icons to storage
|
||||
Iconify.addCollection({
|
||||
prefix,
|
||||
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,
|
||||
});
|
||||
|
||||
// Add one icon separately
|
||||
Iconify.addIcon(prefix + ':id-test', {
|
||||
body:
|
||||
'<defs><path id="ssvg-id-1st-place-medala" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medald" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalf" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalh" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalj" d="M.93.01h120.55v58.36H.93z"/><path id="ssvg-id-1st-place-medalm" d="M.93.01h120.55v58.36H.93z"/><path d="M52.849 78.373v-3.908c3.681-.359 6.25-.958 7.703-1.798c1.454-.84 2.54-2.828 3.257-5.962h4.021v40.385h-5.437V78.373h-9.544z" id="ssvg-id-1st-place-medalp"/><linearGradient x1="49.998%" y1="-13.249%" x2="49.998%" y2="90.002%" id="ssvg-id-1st-place-medalb"><stop stop-color="#1E88E5" offset="13.55%"/><stop stop-color="#1565C0" offset="93.8%"/></linearGradient><linearGradient x1="26.648%" y1="2.735%" x2="77.654%" y2="105.978%" id="ssvg-id-1st-place-medalk"><stop stop-color="#64B5F6" offset="13.55%"/><stop stop-color="#2196F3" offset="94.62%"/></linearGradient><radialGradient cx="22.368%" cy="12.5%" fx="22.368%" fy="12.5%" r="95.496%" id="ssvg-id-1st-place-medalo"><stop stop-color="#FFEB3B" offset="29.72%"/><stop stop-color="#FBC02D" offset="95.44%"/></radialGradient></defs><g fill="none" fill-rule="evenodd"><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medalc" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medala"/></mask><path fill="url(#ssvg-id-1st-place-medalb)" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medalc)" d="M45.44 42.18h31.43l30-48.43H75.44z"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medale" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medald"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medale)" fill="#424242" fill-rule="nonzero"><path d="M101.23-3L75.2 39H50.85L77.11-3h24.12zm5.64-3H75.44l-30 48h31.42l30.01-48z"/></g></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medalg" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalf"/></mask><path d="M79 30H43c-4.42 0-8 3.58-8 8v16.04c0 2.17 1.8 3.95 4.02 3.96h.01c2.23-.01 4.97-1.75 4.97-3.96V44c0-1.1.9-2 2-2h30c1.1 0 2 .9 2 2v9.93c0 1.98 2.35 3.68 4.22 4.04c.26.05.52.08.78.08c2.21 0 4-1.79 4-4V38c0-4.42-3.58-8-8-8z" fill="#FDD835" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medalg)"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medali" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalh"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medali)" fill="#424242" fill-rule="nonzero"><path d="M79 32c3.31 0 6 2.69 6 6v16.04A2.006 2.006 0 0 1 82.59 56c-1.18-.23-2.59-1.35-2.59-2.07V44c0-2.21-1.79-4-4-4H46c-2.21 0-4 1.79-4 4v10.04c0 .88-1.64 1.96-2.97 1.96c-1.12-.01-2.03-.89-2.03-1.96V38c0-3.31 2.69-6 6-6h36zm0-2H43c-4.42 0-8 3.58-8 8v16.04c0 2.17 1.8 3.95 4.02 3.96h.01c2.23-.01 4.97-1.75 4.97-3.96V44c0-1.1.9-2 2-2h30c1.1 0 2 .9 2 2v9.93c0 1.98 2.35 3.68 4.22 4.04c.26.05.52.08.78.08c2.21 0 4-1.79 4-4V38c0-4.42-3.58-8-8-8z"/></g></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medall" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalj"/></mask><path fill="url(#ssvg-id-1st-place-medalk)" fill-rule="nonzero" mask="url(#ssvg-id-1st-place-medall)" d="M76.87 42.18H45.44l-30-48.43h31.43z"/></g><g transform="translate(3 4)"><mask id="ssvg-id-1st-place-medaln" fill="#fff"><use xlink:href="#ssvg-id-1st-place-medalm"/></mask><g opacity=".2" mask="url(#ssvg-id-1st-place-medaln)" fill="#424242" fill-rule="nonzero"><path d="M45.1-3l26.35 42H47.1L20.86-3H45.1zm1.77-3H15.44l30 48h31.42L46.87-6z"/></g></g><circle fill="url(#ssvg-id-1st-place-medalo)" fill-rule="nonzero" cx="64" cy="86" r="38"/><path d="M64 51c19.3 0 35 15.7 35 35s-15.7 35-35 35s-35-15.7-35-35s15.7-35 35-35zm0-3c-20.99 0-38 17.01-38 38s17.01 38 38 38s38-17.01 38-38s-17.01-38-38-38z" opacity=".2" fill="#424242" fill-rule="nonzero"/><path d="M47.3 63.59h33.4v44.4H47.3z"/><use fill="#000" xlink:href="#ssvg-id-1st-place-medalp"/><use fill="#FFA000" xlink:href="#ssvg-id-1st-place-medalp"/></g>',
|
||||
width: 128,
|
||||
height: 128,
|
||||
});
|
||||
|
||||
it('Rendering icons without API', (done) => {
|
||||
node1.innerHTML =
|
||||
'<div><p>Testing Iconify without API</p>' +
|
||||
' <span class="iconify-inline" data-icon="' +
|
||||
prefix +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="' +
|
||||
prefix +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
' <i class="iconify" data-icon="' +
|
||||
prefix +
|
||||
':account-cash" title="<Cash>!"></i>' +
|
||||
' <span class="iconify" data-icon="' +
|
||||
prefix +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></span>' +
|
||||
' <span class="iconify" data-icon="' +
|
||||
prefix +
|
||||
':id-test"></span>' +
|
||||
'</div>';
|
||||
|
||||
node2.innerHTML =
|
||||
'<div><p>This node should not be replaced</p>' +
|
||||
'<span class="iconify" data-icon="' +
|
||||
prefix +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>';
|
||||
|
||||
// Icons should not have been replaced yet
|
||||
let list = node1.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(5);
|
||||
|
||||
list = node2.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(1);
|
||||
|
||||
// Check in ticks
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
list = node1.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(0);
|
||||
|
||||
list = node2.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(1);
|
||||
|
||||
// Test SVG with ID
|
||||
const idTest = node1.querySelector('#ssvg-id-1st-place-medala');
|
||||
expect(idTest).to.be.equal(null, 'Expecting ID to be replaced');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
100
packages/browser-tests/tests/fake-api.ts
Normal file
100
packages/browser-tests/tests/fake-api.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { RedundancyPendingItem } from '@cyberalien/redundancy';
|
||||
import {
|
||||
APIQueryParams,
|
||||
IconifyAPIPrepareQuery,
|
||||
IconifyAPISendQuery,
|
||||
} from '@iconify/core/lib/api/modules';
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
|
||||
/**
|
||||
* Fake data entry
|
||||
*/
|
||||
export interface FakeData {
|
||||
icons: string[];
|
||||
host?: string; // host to respond to
|
||||
delay?: number; // 0 = instant reply
|
||||
data: IconifyJSON; // data to send
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake data storage
|
||||
*/
|
||||
const fakeData: Record<string, FakeData[]> = Object.create(null);
|
||||
|
||||
export function setFakeData(prefix: string, item: FakeData): void {
|
||||
if (fakeData[prefix] === void 0) {
|
||||
fakeData[prefix] = [];
|
||||
}
|
||||
fakeData[prefix].push(item);
|
||||
}
|
||||
|
||||
interface FakeAPIQueryParams extends APIQueryParams {
|
||||
data: FakeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare params
|
||||
*/
|
||||
export const prepareQuery: IconifyAPIPrepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
// Find items that have query
|
||||
const items: APIQueryParams[] = [];
|
||||
let missing = icons.slice(0);
|
||||
|
||||
if (fakeData[prefix] !== void 0) {
|
||||
fakeData[prefix].forEach(item => {
|
||||
const matches = item.icons.filter(
|
||||
icon => missing.indexOf(icon) !== -1
|
||||
);
|
||||
if (!matches.length) {
|
||||
// No match
|
||||
return;
|
||||
}
|
||||
|
||||
// Contains at least one matching icon
|
||||
missing = missing.filter(icon => matches.indexOf(icon) === -1);
|
||||
const query: FakeAPIQueryParams = {
|
||||
prefix,
|
||||
icons: matches,
|
||||
data: item,
|
||||
};
|
||||
items.push(query);
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load icons
|
||||
*/
|
||||
export const sendQuery: IconifyAPISendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
const prefix = params.prefix;
|
||||
const icons = params.icons;
|
||||
|
||||
const data = (params as FakeAPIQueryParams).data;
|
||||
if (!data) {
|
||||
throw new Error('Fake data is missing in query params');
|
||||
}
|
||||
if (typeof data.host === 'string' && data.host !== host) {
|
||||
// Host mismatch - do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
const sendResponse = () => {
|
||||
console.log('Sending data for prefix "' + prefix + '", icons:', icons);
|
||||
status.done(data.data);
|
||||
};
|
||||
|
||||
if (!data.delay) {
|
||||
sendResponse();
|
||||
} else {
|
||||
setTimeout(sendResponse, data.delay);
|
||||
}
|
||||
};
|
14
packages/browser-tests/tests/node.ts
Normal file
14
packages/browser-tests/tests/node.ts
Normal file
@ -0,0 +1,14 @@
|
||||
let counter = 0;
|
||||
|
||||
/**
|
||||
* Create node for test
|
||||
*/
|
||||
export function getNode(prefix = 'test') {
|
||||
const id = prefix + '-' + Date.now() + '-' + counter++;
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.setAttribute('id', id);
|
||||
|
||||
document.getElementById('debug').appendChild(node);
|
||||
return node;
|
||||
}
|
36
packages/browser-tests/tests/tests.html
Normal file
36
packages/browser-tests/tests/tests.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
|
||||
<meta name="viewport" content="width=1024" />
|
||||
<title>Tests</title>
|
||||
<link href="../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
<style>
|
||||
#debug {
|
||||
display: none;
|
||||
}
|
||||
#debug > div {
|
||||
margin: 8px 0;
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<div id="debug"></div>
|
||||
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<!-- tests -->
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
14
packages/browser-tests/tsconfig.json
Normal file
14
packages/browser-tests/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./tests",
|
||||
"outDir": "./lib",
|
||||
"target": "ES2019",
|
||||
"module": "ESNext",
|
||||
"declaration": false,
|
||||
"sourceMap": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
2
packages/core/.eslintignore
Normal file
2
packages/core/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
lib
|
||||
tests-compiled
|
30
packages/core/.eslintrc.js
Normal file
30
packages/core/.eslintrc.js
Normal file
@ -0,0 +1,30 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
mocha: true
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
project: './tsconfig-base.json'
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'no-mixed-spaces-and-tabs': ['off'],
|
||||
'no-unused-vars': ['off'],
|
||||
'@typescript-eslint/no-unused-vars-experimental': ['error']
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['src/**/*.ts', 'tests/**/*.ts']
|
||||
}
|
||||
]
|
||||
};
|
5
packages/core/.gitignore
vendored
Normal file
5
packages/core/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
tests-compiled
|
||||
lib
|
||||
dist
|
1989
packages/core/package-lock.json
generated
Normal file
1989
packages/core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
packages/core/package.json
Normal file
43
packages/core/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@iconify/core",
|
||||
"private": true,
|
||||
"description": "Reusable files used by multiple Iconify packages",
|
||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||
"version": "1.0.0-beta.0",
|
||||
"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": {
|
||||
"clean": "rm -rf lib compiled-tests",
|
||||
"lint": "npx eslint {src,tests}/**/*.ts",
|
||||
"prebuild": "npm run lint",
|
||||
"build": "npx tsc -b",
|
||||
"prewatch": "npm run lint",
|
||||
"watch": "npx tsc -b -w",
|
||||
"test": "npx mocha tests-compiled/*/*-test.js",
|
||||
"pretest": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.12.34",
|
||||
"@types/request": "^2.48.4",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^6.8.0",
|
||||
"mocha": "^6.2.3",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cyberalien/redundancy": "^1.0.0",
|
||||
"@iconify/types": "^1.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"axios": "^0.19.2"
|
||||
}
|
||||
}
|
151
packages/core/src/api/callbacks.ts
Normal file
151
packages/core/src/api/callbacks.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import {
|
||||
IconifyIconLoaderCallback,
|
||||
IconifyIconLoaderAbort,
|
||||
} from '../interfaces/loader';
|
||||
import { getStorage } from '../storage';
|
||||
import { SortedIcons } from '../icon/sort';
|
||||
|
||||
/**
|
||||
* Storage for callbacks
|
||||
*/
|
||||
interface CallbackItem {
|
||||
// id
|
||||
id: number;
|
||||
|
||||
// Icons
|
||||
icons: SortedIcons;
|
||||
|
||||
// Callback to call on any update
|
||||
callback: IconifyIconLoaderCallback;
|
||||
|
||||
// Callback to call to remove item from queue
|
||||
abort: IconifyIconLoaderAbort;
|
||||
}
|
||||
|
||||
// This export is only for unit testing, should not be used
|
||||
export const callbacks: Record<string, CallbackItem[]> = Object.create(null);
|
||||
const pendingUpdates: Record<string, boolean> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Remove callback
|
||||
*/
|
||||
function removeCallback(prefixes: string[], id: number): void {
|
||||
prefixes.forEach(prefix => {
|
||||
const items = callbacks[prefix];
|
||||
if (items) {
|
||||
callbacks[prefix] = items.filter(row => row.id !== id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all callbacks for prefix
|
||||
*/
|
||||
export function updateCallbacks(prefix: string): void {
|
||||
if (!pendingUpdates[prefix]) {
|
||||
pendingUpdates[prefix] = true;
|
||||
setTimeout(() => {
|
||||
pendingUpdates[prefix] = false;
|
||||
|
||||
if (callbacks[prefix] === void 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all items
|
||||
const items = callbacks[prefix].slice(0);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const storage = getStorage(prefix);
|
||||
|
||||
// Check each item for changes
|
||||
let hasPending = false;
|
||||
items.forEach((item: CallbackItem) => {
|
||||
const icons = item.icons;
|
||||
const oldLength = icons.pending.length;
|
||||
icons.pending = icons.pending.filter(icon => {
|
||||
if (icon.prefix !== prefix) {
|
||||
// Checking only current prefix
|
||||
return true;
|
||||
}
|
||||
|
||||
const name = icon.name;
|
||||
if (storage.icons[name] !== void 0) {
|
||||
// Loaded
|
||||
icons.loaded.push({
|
||||
prefix,
|
||||
name,
|
||||
});
|
||||
} else if (storage.missing[name] !== void 0) {
|
||||
// Missing
|
||||
icons.missing.push({
|
||||
prefix,
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
// Pending
|
||||
hasPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Changes detected - call callback
|
||||
if (icons.pending.length !== oldLength) {
|
||||
if (!hasPending) {
|
||||
// All icons have been loaded - remove callback from prefix
|
||||
removeCallback([prefix], item.id);
|
||||
}
|
||||
item.callback(
|
||||
icons.loaded.slice(0),
|
||||
icons.missing.slice(0),
|
||||
icons.pending.slice(0),
|
||||
item.abort
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique id counter for callbacks
|
||||
*/
|
||||
let idCounter = 0;
|
||||
|
||||
/**
|
||||
* Add callback
|
||||
*/
|
||||
export function storeCallback(
|
||||
callback: IconifyIconLoaderCallback,
|
||||
icons: SortedIcons,
|
||||
pendingPrefixes: string[]
|
||||
): IconifyIconLoaderAbort {
|
||||
// Create unique id and abort function
|
||||
const id = idCounter++;
|
||||
const abort = removeCallback.bind(null, pendingPrefixes, id);
|
||||
|
||||
if (!icons.pending.length) {
|
||||
// Do not store item without pending icons and return function that does nothing
|
||||
return abort;
|
||||
}
|
||||
|
||||
// Create item and store it for all pending prefixes
|
||||
const item: CallbackItem = {
|
||||
id,
|
||||
icons,
|
||||
callback,
|
||||
abort: abort,
|
||||
};
|
||||
|
||||
pendingPrefixes.forEach(prefix => {
|
||||
if (callbacks[prefix] === void 0) {
|
||||
callbacks[prefix] = [];
|
||||
}
|
||||
callbacks[prefix].push(item);
|
||||
});
|
||||
|
||||
return abort;
|
||||
}
|
127
packages/core/src/api/config.ts
Normal file
127
packages/core/src/api/config.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { RedundancyConfig } from '@cyberalien/redundancy';
|
||||
import { merge } from '../misc/merge';
|
||||
|
||||
/**
|
||||
* API config
|
||||
*/
|
||||
export interface IconifyAPIConfig extends RedundancyConfig {
|
||||
// Root path, after domain name and before prefix
|
||||
path: string;
|
||||
|
||||
// URL length limit
|
||||
maxURL: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local storage interfaces
|
||||
*/
|
||||
interface ConfigStorage {
|
||||
// API configuration for all prefixes
|
||||
default: IconifyAPIConfig;
|
||||
|
||||
// Prefix specific API configuration
|
||||
prefixes: Record<string, IconifyAPIConfig>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redundancy for API servers.
|
||||
*
|
||||
* API should have very high uptime because of implemented redundancy at server level, but
|
||||
* sometimes bad things happen. On internet 100% uptime is not possible.
|
||||
*
|
||||
* There could be routing problems. Server might go down for whatever reason, but it takes
|
||||
* few minutes to detect that downtime, so during those few minutes API might not be accessible.
|
||||
*
|
||||
* This script has some redundancy to mitigate possible network issues.
|
||||
*
|
||||
* If one host cannot be reached in 'rotate' (750 by default) ms, script will try to retrieve
|
||||
* data from different host. Hosts have different configurations, pointing to different
|
||||
* API servers hosted at different providers.
|
||||
*/
|
||||
const fallBackAPISources = [
|
||||
'https://api.simplesvg.com',
|
||||
'https://api.unisvg.com',
|
||||
];
|
||||
|
||||
// Shuffle fallback API
|
||||
const fallBackAPI: string[] = [];
|
||||
while (fallBackAPISources.length > 0) {
|
||||
if (fallBackAPISources.length === 1) {
|
||||
fallBackAPI.push(fallBackAPISources.shift() as string);
|
||||
} else {
|
||||
// Get first or last item
|
||||
if (Math.random() > 0.5) {
|
||||
fallBackAPI.push(fallBackAPISources.shift() as string);
|
||||
} else {
|
||||
fallBackAPI.push(fallBackAPISources.pop() as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration
|
||||
*/
|
||||
const defaultConfig: IconifyAPIConfig = {
|
||||
// API hosts
|
||||
resources: ['https://api.iconify.design'].concat(fallBackAPI),
|
||||
|
||||
// Root path
|
||||
path: '/',
|
||||
|
||||
// URL length limit
|
||||
maxURL: 500,
|
||||
|
||||
// Timeout before next host is used.
|
||||
rotate: 750,
|
||||
|
||||
// Timeout to retry same host.
|
||||
timeout: 5000,
|
||||
|
||||
// Number of attempts for each host.
|
||||
limit: 2,
|
||||
|
||||
// Randomise default API end point.
|
||||
random: false,
|
||||
|
||||
// Start index
|
||||
index: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Local storage
|
||||
*/
|
||||
const configStorage: ConfigStorage = {
|
||||
default: defaultConfig,
|
||||
prefixes: Object.create(null),
|
||||
};
|
||||
|
||||
/**
|
||||
* Add custom config for prefix(es)
|
||||
*
|
||||
* This function should be used before any API queries.
|
||||
* On first API query computed configuration will be cached, so changes to config will not take effect.
|
||||
*/
|
||||
export function setAPIConfig(
|
||||
customConfig: Partial<IconifyAPIConfig>,
|
||||
prefix?: string | string[]
|
||||
): void {
|
||||
const mergedConfig = merge(
|
||||
configStorage.default,
|
||||
customConfig
|
||||
) as IconifyAPIConfig;
|
||||
if (prefix === void 0) {
|
||||
configStorage.default = mergedConfig;
|
||||
return;
|
||||
}
|
||||
(typeof prefix === 'string' ? [prefix] : prefix).forEach(prefix => {
|
||||
configStorage.prefixes[prefix] = mergedConfig;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API configuration
|
||||
*/
|
||||
export function getAPIConfig(prefix: string): IconifyAPIConfig | null {
|
||||
const value = configStorage.prefixes[prefix];
|
||||
return value === void 0 ? configStorage.default : value;
|
||||
}
|
279
packages/core/src/api/index.ts
Normal file
279
packages/core/src/api/index.ts
Normal file
@ -0,0 +1,279 @@
|
||||
import {
|
||||
Redundancy,
|
||||
initRedundancy,
|
||||
RedundancyQueryCallback,
|
||||
} from '@cyberalien/redundancy';
|
||||
import { SortedIcons, sortIcons } from '../icon/sort';
|
||||
import {
|
||||
IconifyIconLoaderAbort,
|
||||
IconifyIconLoaderCallback,
|
||||
IconifyLoadIcons,
|
||||
} from '../interfaces/loader';
|
||||
import { IsPending, IconifyAPI } from '../interfaces/api';
|
||||
import { storeCallback, updateCallbacks } from './callbacks';
|
||||
import { getAPIModule } from './modules';
|
||||
import { getAPIConfig, IconifyAPIConfig } from './config';
|
||||
import { getStorage, addIconSet } from '../storage';
|
||||
import { coreModules } from '../modules';
|
||||
import { IconifyIconName } from '../icon/name';
|
||||
import { listToIcons, getPrefixes } from '../icon/list';
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
|
||||
// Empty abort callback for loadIcons()
|
||||
function emptyCallback(): void {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* List of icons that are being loaded.
|
||||
*
|
||||
* Icons are added to this list when they are being checked and
|
||||
* removed from this list when they are added to storage as
|
||||
* either an icon or a missing icon. This way same icon should
|
||||
* never be requested twice.
|
||||
*
|
||||
* [prefix][icon] = time when icon was added to queue
|
||||
*/
|
||||
type PendingIcons = Record<string, number>;
|
||||
const pendingIcons: Record<string, PendingIcons> = Object.create(null);
|
||||
|
||||
/**
|
||||
* List of icons that are waiting to be loaded.
|
||||
*
|
||||
* List is passed to API module, then cleared.
|
||||
*
|
||||
* This list should not be used for any checks, use pendingIcons to check
|
||||
* if icons is being loaded.
|
||||
*
|
||||
* [prefix] = array of icon names
|
||||
*/
|
||||
const iconsToLoad: Record<string, string[]> = Object.create(null);
|
||||
|
||||
// Flags to merge multiple synchronous icon requests in one asynchronous request
|
||||
const loaderFlags: Record<string, boolean> = Object.create(null);
|
||||
const queueFlags: Record<string, boolean> = Object.create(null);
|
||||
|
||||
// Redundancy instances cache
|
||||
interface LocalCache {
|
||||
config: IconifyAPIConfig | null;
|
||||
redundancy: Redundancy | null;
|
||||
}
|
||||
const redundancyCache: Record<string, LocalCache> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Function called when new icons have been loaded
|
||||
*/
|
||||
function loadedNewIcons(prefix: string): void {
|
||||
// Run only once per tick, possibly joining multiple API responses in one call
|
||||
if (!loaderFlags[prefix]) {
|
||||
loaderFlags[prefix] = true;
|
||||
setTimeout(() => {
|
||||
loaderFlags[prefix] = false;
|
||||
updateCallbacks(prefix);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load icons
|
||||
*/
|
||||
function loadNewIcons(prefix: string, icons: string[]): void {
|
||||
function err(): void {
|
||||
console.error(
|
||||
'Unable to retrieve icons for prefix "' +
|
||||
prefix +
|
||||
'" because API is not configured properly.'
|
||||
);
|
||||
}
|
||||
|
||||
// Add icons to queue
|
||||
if (iconsToLoad[prefix] === void 0) {
|
||||
iconsToLoad[prefix] = icons;
|
||||
} else {
|
||||
iconsToLoad[prefix] = iconsToLoad[prefix].concat(icons).sort();
|
||||
}
|
||||
|
||||
// Trigger update on next tick, mering multiple synchronous requests into one asynchronous request
|
||||
if (!queueFlags[prefix]) {
|
||||
queueFlags[prefix] = true;
|
||||
setTimeout(() => {
|
||||
queueFlags[prefix] = false;
|
||||
|
||||
// Get icons and delete queue
|
||||
const icons = iconsToLoad[prefix];
|
||||
delete iconsToLoad[prefix];
|
||||
|
||||
// Get API module
|
||||
const api = getAPIModule(prefix);
|
||||
if (!api) {
|
||||
// No way to load icons!
|
||||
err();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get Redundancy instance
|
||||
if (redundancyCache[prefix] === void 0) {
|
||||
const config = getAPIConfig(prefix);
|
||||
|
||||
// Attempt to find matching instance from other prefixes
|
||||
// Using same Redundancy instance allows keeping track of failed hosts for multiple prefixes
|
||||
for (const prefix2 in redundancyCache) {
|
||||
const item = redundancyCache[prefix2];
|
||||
if (item.config === config) {
|
||||
redundancyCache[prefix] = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (redundancyCache[prefix] === void 0) {
|
||||
redundancyCache[prefix] = {
|
||||
config,
|
||||
redundancy: config ? initRedundancy(config) : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
const redundancy = redundancyCache[prefix].redundancy;
|
||||
if (!redundancy) {
|
||||
// No way to load icons because configuration is not set!
|
||||
err();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare parameters and run queries
|
||||
const params = api.prepare(prefix, icons);
|
||||
params.forEach((item) => {
|
||||
redundancy.query(
|
||||
item,
|
||||
api.send as RedundancyQueryCallback,
|
||||
(data) => {
|
||||
// Add icons to storage
|
||||
const storage = getStorage(prefix);
|
||||
try {
|
||||
const added = addIconSet(
|
||||
storage,
|
||||
data as IconifyJSON,
|
||||
'all'
|
||||
);
|
||||
if (typeof added === 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove added icons from pending list
|
||||
const pending = pendingIcons[prefix];
|
||||
added.forEach((name) => {
|
||||
delete pending[name];
|
||||
});
|
||||
|
||||
// Cache API response
|
||||
if (coreModules.cache) {
|
||||
coreModules.cache(data as IconifyJSON);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Trigger update on next tick
|
||||
loadedNewIcons(prefix);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if icon is being loaded
|
||||
*/
|
||||
const isPending: IsPending = (prefix: string, icon: string): boolean => {
|
||||
return (
|
||||
pendingIcons[prefix] !== void 0 && pendingIcons[prefix][icon] !== void 0
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load icons
|
||||
*/
|
||||
const loadIcons: IconifyLoadIcons = (
|
||||
icons: (IconifyIconName | string)[],
|
||||
callback?: IconifyIconLoaderCallback
|
||||
): IconifyIconLoaderAbort => {
|
||||
// Clean up and copy icons list
|
||||
const cleanedIcons = listToIcons(icons, true);
|
||||
|
||||
// Sort icons by missing/loaded/pending
|
||||
// Pending means icon is either being requsted or is about to be requested
|
||||
const sortedIcons: SortedIcons = sortIcons(cleanedIcons);
|
||||
|
||||
if (!sortedIcons.pending.length) {
|
||||
// Nothing to load
|
||||
let callCallback = true;
|
||||
|
||||
if (callback) {
|
||||
setTimeout(() => {
|
||||
if (callCallback) {
|
||||
callback(
|
||||
sortedIcons.loaded,
|
||||
sortedIcons.missing,
|
||||
sortedIcons.pending,
|
||||
emptyCallback
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return (): void => {
|
||||
callCallback = false;
|
||||
};
|
||||
}
|
||||
|
||||
// Get all prefixes
|
||||
const prefixes = getPrefixes(sortedIcons.pending);
|
||||
|
||||
// Get pending icons queue for prefix and create new icons list
|
||||
const newIcons: Record<string, string[]> = Object.create(null);
|
||||
prefixes.forEach((prefix) => {
|
||||
if (pendingIcons[prefix] === void 0) {
|
||||
pendingIcons[prefix] = Object.create(null);
|
||||
}
|
||||
newIcons[prefix] = [];
|
||||
});
|
||||
|
||||
// List of new icons
|
||||
const time = Date.now();
|
||||
|
||||
// Filter pending icons list: find icons that are not being loaded yet
|
||||
// If icon was called before, it must exist in pendingIcons or storage, but because this
|
||||
// function is called right after sortIcons() that checks storage, icon is definitely not in storage.
|
||||
sortedIcons.pending.forEach((icon) => {
|
||||
const prefix = icon.prefix;
|
||||
const name = icon.name;
|
||||
|
||||
const pendingQueue = pendingIcons[prefix];
|
||||
if (pendingQueue[name] === void 0) {
|
||||
// New icon - add to pending queue to mark it as being loaded
|
||||
pendingQueue[name] = time;
|
||||
// Add it to new icons list to pass it to API module for loading
|
||||
newIcons[prefix].push(name);
|
||||
}
|
||||
});
|
||||
|
||||
// Load icons on next tick to make sure result is not returned before callback is stored and
|
||||
// to consolidate multiple synchronous loadIcons() calls into one asynchronous API call
|
||||
prefixes.forEach((prefix) => {
|
||||
if (newIcons[prefix].length) {
|
||||
loadNewIcons(prefix, newIcons[prefix]);
|
||||
}
|
||||
});
|
||||
|
||||
// Store callback and return abort function
|
||||
return callback
|
||||
? storeCallback(callback, sortedIcons, prefixes)
|
||||
: emptyCallback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Export module
|
||||
*/
|
||||
export const API: IconifyAPI = {
|
||||
isPending,
|
||||
loadIcons,
|
||||
};
|
75
packages/core/src/api/modules.ts
Normal file
75
packages/core/src/api/modules.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { RedundancyPendingItem } from '@cyberalien/redundancy';
|
||||
|
||||
/**
|
||||
* Params for sendQuery()
|
||||
*/
|
||||
export interface APIQueryParams {
|
||||
prefix: string;
|
||||
icons: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions to implement in module
|
||||
*/
|
||||
export type IconifyAPIPrepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
) => APIQueryParams[];
|
||||
|
||||
export type IconifyAPISendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* API modules
|
||||
*/
|
||||
export interface IconifyAPIModule {
|
||||
prepare: IconifyAPIPrepareQuery;
|
||||
send: IconifyAPISendQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local storate types and entries
|
||||
*/
|
||||
interface ModuleStorage {
|
||||
default: IconifyAPIModule | null;
|
||||
prefixes: Record<string, IconifyAPIModule>;
|
||||
}
|
||||
|
||||
const storage: ModuleStorage = {
|
||||
default: null,
|
||||
prefixes: Object.create(null),
|
||||
};
|
||||
|
||||
/**
|
||||
* Set API module
|
||||
*
|
||||
* If prefix is not set, function sets default method.
|
||||
* If prefix is a string or array of strings, function sets method only for those prefixes.
|
||||
*
|
||||
* This should be used before sending any API requests. If used after sending API request, method
|
||||
* is already cached so changing callback will not have any effect.
|
||||
*/
|
||||
export function setAPIModule(
|
||||
item: IconifyAPIModule,
|
||||
prefix?: string | string[]
|
||||
): void {
|
||||
if (prefix === void 0) {
|
||||
storage.default = item;
|
||||
return;
|
||||
}
|
||||
|
||||
(typeof prefix === 'string' ? [prefix] : prefix).forEach(prefix => {
|
||||
storage.prefixes[prefix] = item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API module
|
||||
*/
|
||||
export function getAPIModule(prefix: string): IconifyAPIModule | null {
|
||||
const value = storage.prefixes[prefix];
|
||||
return value === void 0 ? storage.default : value;
|
||||
}
|
66
packages/core/src/builder/calc-size.ts
Normal file
66
packages/core/src/builder/calc-size.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Regular expressions for calculating dimensions
|
||||
*/
|
||||
const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;
|
||||
const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;
|
||||
|
||||
/**
|
||||
* Calculate second dimension when only 1 dimension is set
|
||||
*
|
||||
* @param {string|number} size One dimension (such as width)
|
||||
* @param {number} ratio Width/height ratio.
|
||||
* If size is width, ratio = height/width
|
||||
* If size is height, ratio = width/height
|
||||
* @param {number} [precision] Floating number precision in result to minimize output. Default = 2
|
||||
* @return {string|number} Another dimension
|
||||
*/
|
||||
export function calcSize(
|
||||
size: string | number,
|
||||
ratio: number,
|
||||
precision?: number
|
||||
): string | number {
|
||||
if (ratio === 1) {
|
||||
return size;
|
||||
}
|
||||
|
||||
precision = precision === void 0 ? 100 : precision;
|
||||
if (typeof size === 'number') {
|
||||
return Math.ceil(size * ratio * precision) / precision;
|
||||
}
|
||||
|
||||
if (typeof size !== 'string') {
|
||||
return size;
|
||||
}
|
||||
|
||||
// Split code into sets of strings and numbers
|
||||
const oldParts = size.split(unitsSplit);
|
||||
if (oldParts === null || !oldParts.length) {
|
||||
return size;
|
||||
}
|
||||
|
||||
const newParts = [];
|
||||
let code = oldParts.shift() as string;
|
||||
let isNumber = unitsTest.test(code as string);
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (isNumber) {
|
||||
const num = parseFloat(code);
|
||||
if (isNaN(num)) {
|
||||
newParts.push(code);
|
||||
} else {
|
||||
newParts.push(Math.ceil(num * ratio * precision) / precision);
|
||||
}
|
||||
} else {
|
||||
newParts.push(code);
|
||||
}
|
||||
|
||||
// next
|
||||
code = oldParts.shift() as string;
|
||||
if (code === void 0) {
|
||||
return newParts.join('');
|
||||
}
|
||||
|
||||
isNumber = !isNumber;
|
||||
}
|
||||
}
|
66
packages/core/src/builder/ids.ts
Normal file
66
packages/core/src/builder/ids.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Regular expression for finding ids
|
||||
*/
|
||||
const regex = /\sid="(\S+)"/g;
|
||||
|
||||
/**
|
||||
* New random-ish prefix for ids
|
||||
*/
|
||||
const randomPrefix =
|
||||
'IconifyId-' +
|
||||
Date.now().toString(16) +
|
||||
'-' +
|
||||
((Math.random() * 0x1000000) | 0).toString(16) +
|
||||
'-';
|
||||
|
||||
/**
|
||||
* Counter for ids, increasing with every replacement
|
||||
*/
|
||||
let counter = 0;
|
||||
|
||||
/**
|
||||
* Replace multiple occurance of same string
|
||||
*/
|
||||
function strReplace(search: string, replace: string, subject: string): string {
|
||||
let pos = 0;
|
||||
|
||||
while ((pos = subject.indexOf(search, pos)) !== -1) {
|
||||
subject =
|
||||
subject.slice(0, pos) +
|
||||
replace +
|
||||
subject.slice(pos + search.length);
|
||||
pos += replace.length;
|
||||
}
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace IDs in SVG output with unique IDs
|
||||
* Fast replacement without parsing XML, assuming commonly used patterns and clean XML (icon should have been cleaned up with Iconify Tools or SVGO).
|
||||
*/
|
||||
export function replaceIDs(
|
||||
body: string,
|
||||
prefix: string | (() => string) = randomPrefix
|
||||
): string {
|
||||
// Find all IDs
|
||||
const ids: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = regex.exec(body))) {
|
||||
ids.push(match[1]);
|
||||
}
|
||||
if (!ids.length) {
|
||||
return body;
|
||||
}
|
||||
|
||||
// Replace with unique ids
|
||||
ids.forEach(id => {
|
||||
const newID =
|
||||
typeof prefix === 'function' ? prefix() : prefix + counter++;
|
||||
body = strReplace('="' + id + '"', '="' + newID + '"', body);
|
||||
body = strReplace('="#' + id + '"', '="#' + newID + '"', body);
|
||||
body = strReplace('(#' + id + ')', '(#' + newID + ')', body);
|
||||
});
|
||||
|
||||
return body;
|
||||
}
|
220
packages/core/src/builder/index.ts
Normal file
220
packages/core/src/builder/index.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import { FullIconifyIcon } from '../icon';
|
||||
import { FullIconCustomisations } from '../customisations';
|
||||
import { calcSize } from './calc-size';
|
||||
|
||||
/**
|
||||
* Get preserveAspectRatio value
|
||||
*/
|
||||
function preserveAspectRatio(props: FullIconCustomisations): string {
|
||||
let result = '';
|
||||
switch (props.hAlign) {
|
||||
case 'left':
|
||||
result += 'xMin';
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
result += 'xMax';
|
||||
break;
|
||||
|
||||
default:
|
||||
result += 'xMid';
|
||||
}
|
||||
switch (props.vAlign) {
|
||||
case 'top':
|
||||
result += 'YMin';
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
result += 'YMax';
|
||||
break;
|
||||
|
||||
default:
|
||||
result += 'YMid';
|
||||
}
|
||||
result += props.slice ? ' slice' : ' meet';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for getSVGData() result
|
||||
*/
|
||||
export interface IconifyIconBuildResult {
|
||||
attributes: {
|
||||
// Attributes for <svg>
|
||||
width: string;
|
||||
height: string;
|
||||
preserveAspectRatio: string;
|
||||
viewBox: string;
|
||||
};
|
||||
// Content
|
||||
body: string;
|
||||
// True if 'vertical-align: -0.125em' or equivalent should be added by implementation
|
||||
inline?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for viewBox
|
||||
*/
|
||||
interface ViewBox {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG attributes and content from icon + customisations
|
||||
*
|
||||
* Does not generate style to make it compatible with frameworks that use objects for style, such as React.
|
||||
* Instead, it generates verticalAlign value that should be added to style.
|
||||
*
|
||||
* Customisations should be normalised by platform specific parser.
|
||||
* Result should be converted to <svg> by platform specific parser.
|
||||
* Use replaceIDs to generate unique IDs for body.
|
||||
*/
|
||||
export function iconToSVG(
|
||||
icon: FullIconifyIcon,
|
||||
customisations: FullIconCustomisations
|
||||
): IconifyIconBuildResult {
|
||||
// viewBox
|
||||
const box: ViewBox = {
|
||||
left: icon.left,
|
||||
top: icon.top,
|
||||
width: icon.width,
|
||||
height: icon.height,
|
||||
};
|
||||
|
||||
// Apply transformations
|
||||
const transformations: string[] = [];
|
||||
let rotation = customisations.rotate;
|
||||
|
||||
if (customisations.hFlip) {
|
||||
if (customisations.vFlip) {
|
||||
rotation += 2;
|
||||
} else {
|
||||
// Horizontal flip
|
||||
transformations.push(
|
||||
'translate(' +
|
||||
(box.width + box.left) +
|
||||
' ' +
|
||||
(0 - box.top) +
|
||||
')'
|
||||
);
|
||||
transformations.push('scale(-1 1)');
|
||||
box.top = box.left = 0;
|
||||
}
|
||||
} else if (customisations.vFlip) {
|
||||
// Vertical flip
|
||||
transformations.push(
|
||||
'translate(' + (0 - box.left) + ' ' + (box.height + box.top) + ')'
|
||||
);
|
||||
transformations.push('scale(1 -1)');
|
||||
box.top = box.left = 0;
|
||||
}
|
||||
|
||||
let tempValue: number;
|
||||
rotation = rotation % 4;
|
||||
switch (rotation) {
|
||||
case 1:
|
||||
// 90deg
|
||||
tempValue = box.height / 2 + box.top;
|
||||
transformations.unshift(
|
||||
'rotate(90 ' + tempValue + ' ' + tempValue + ')'
|
||||
);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 180deg
|
||||
transformations.unshift(
|
||||
'rotate(180 ' +
|
||||
(box.width / 2 + box.left) +
|
||||
' ' +
|
||||
(box.height / 2 + box.top) +
|
||||
')'
|
||||
);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// 270deg
|
||||
tempValue = box.width / 2 + box.left;
|
||||
transformations.unshift(
|
||||
'rotate(-90 ' + tempValue + ' ' + tempValue + ')'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (rotation % 2 === 1) {
|
||||
// Swap width/height and x/y for 90deg or 270deg rotation
|
||||
if (box.left !== 0 || box.top !== 0) {
|
||||
tempValue = box.left;
|
||||
box.left = box.top;
|
||||
box.top = tempValue;
|
||||
}
|
||||
if (box.width !== box.height) {
|
||||
tempValue = box.width;
|
||||
box.width = box.height;
|
||||
box.height = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate dimensions
|
||||
let width, height;
|
||||
|
||||
if (customisations.width === null && customisations.height === null) {
|
||||
// Set height to '1em', calculate width
|
||||
height = '1em';
|
||||
width = calcSize(height, box.width / box.height);
|
||||
} else if (
|
||||
customisations.width !== null &&
|
||||
customisations.height !== null
|
||||
) {
|
||||
// Values are set
|
||||
width = customisations.width;
|
||||
height = customisations.height;
|
||||
} else if (customisations.height !== null) {
|
||||
// Height is set
|
||||
height = customisations.height;
|
||||
width = calcSize(height, box.width / box.height);
|
||||
} else {
|
||||
// Width is set
|
||||
width = customisations.width as number | string;
|
||||
height = calcSize(width, box.height / box.width);
|
||||
}
|
||||
|
||||
// Check for 'auto'
|
||||
if (width === 'auto') {
|
||||
width = box.width;
|
||||
}
|
||||
if (height === 'auto') {
|
||||
height = box.height;
|
||||
}
|
||||
|
||||
// Convert to string
|
||||
width = typeof width === 'string' ? width : width + '';
|
||||
height = typeof height === 'string' ? height : height + '';
|
||||
|
||||
// Generate body
|
||||
let body = icon.body;
|
||||
if (transformations.length) {
|
||||
body =
|
||||
'<g transform="' + transformations.join(' ') + '">' + body + '</g>';
|
||||
}
|
||||
|
||||
// Result
|
||||
const result: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width,
|
||||
height,
|
||||
preserveAspectRatio: preserveAspectRatio(customisations),
|
||||
viewBox:
|
||||
box.left + ' ' + box.top + ' ' + box.width + ' ' + box.height,
|
||||
},
|
||||
body,
|
||||
};
|
||||
|
||||
if (customisations.inline) {
|
||||
result.inline = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
308
packages/core/src/cache/storage.ts
vendored
Normal file
308
packages/core/src/cache/storage.ts
vendored
Normal file
@ -0,0 +1,308 @@
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
import { CacheIcons, LoadIconsCache } from '../interfaces/cache';
|
||||
import { getStorage, addIconSet } from '../storage';
|
||||
|
||||
interface StorageType<T> {
|
||||
local: T;
|
||||
session: T;
|
||||
}
|
||||
|
||||
type StorageConfig = StorageType<boolean>;
|
||||
type StorageCount = StorageType<number>;
|
||||
type StorageEmptyList = StorageType<number[]>;
|
||||
|
||||
export interface StoredItem {
|
||||
cached: number;
|
||||
data: IconifyJSON;
|
||||
}
|
||||
|
||||
// After changing configuration change it in tests/*/fake_cache.ts
|
||||
|
||||
// Cache version. Bump when structure changes
|
||||
const cacheVersion = 'iconify1';
|
||||
|
||||
// Cache keys
|
||||
const cachePrefix = 'iconify';
|
||||
const countKey = cachePrefix + '-count';
|
||||
const versionKey = cachePrefix + '-version';
|
||||
|
||||
/**
|
||||
* Cache expiration
|
||||
*/
|
||||
const hour = 3600000;
|
||||
const cacheExpiration = 168; // In hours
|
||||
|
||||
/**
|
||||
* Storage configuration
|
||||
*/
|
||||
export const config: StorageConfig = {
|
||||
local: true,
|
||||
session: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Flag to check if storage has been loaded
|
||||
*/
|
||||
let loaded = false;
|
||||
|
||||
/**
|
||||
* Items counter
|
||||
*/
|
||||
export const count: StorageCount = {
|
||||
local: 0,
|
||||
session: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* List of empty items
|
||||
*/
|
||||
export const emptyList: StorageEmptyList = {
|
||||
local: [],
|
||||
session: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Fake window for unit testing
|
||||
*/
|
||||
type FakeWindow = Record<string, typeof localStorage>;
|
||||
|
||||
let _window: FakeWindow =
|
||||
typeof window === 'undefined' ? {} : ((window as unknown) as FakeWindow);
|
||||
export function mock(fakeWindow: FakeWindow): void {
|
||||
loaded = false;
|
||||
_window = fakeWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
function getGlobal(key: keyof StorageConfig): typeof localStorage | null {
|
||||
const attr = key + 'Storage';
|
||||
try {
|
||||
if (
|
||||
_window &&
|
||||
_window[attr] &&
|
||||
typeof _window[attr].length === 'number'
|
||||
) {
|
||||
return _window[attr];
|
||||
}
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
// Failed - mark as disabled
|
||||
config[key] = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change current count for storage
|
||||
*/
|
||||
function setCount(
|
||||
storage: typeof localStorage,
|
||||
key: keyof StorageConfig,
|
||||
value: number
|
||||
): boolean {
|
||||
try {
|
||||
storage.setItem(countKey, value + '');
|
||||
count[key] = value;
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current count from storage
|
||||
*
|
||||
* @param storage
|
||||
*/
|
||||
function getCount(storage: typeof localStorage): number {
|
||||
const count = storage.getItem(countKey);
|
||||
if (count) {
|
||||
const total = parseInt(count);
|
||||
return total ? total : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize storage
|
||||
*
|
||||
* @param storage
|
||||
* @param key
|
||||
*/
|
||||
function initCache(
|
||||
storage: typeof localStorage,
|
||||
key: keyof StorageConfig
|
||||
): void {
|
||||
try {
|
||||
storage.setItem(versionKey, cacheVersion);
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
setCount(storage, key, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy old cache
|
||||
*
|
||||
* @param storage
|
||||
*/
|
||||
function destroyCache(storage: typeof localStorage): void {
|
||||
try {
|
||||
const total = getCount(storage);
|
||||
for (let i = 0; i < total; i++) {
|
||||
storage.removeItem(cachePrefix + i);
|
||||
}
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load icons from cache
|
||||
*/
|
||||
export const loadCache: LoadIconsCache = (): void => {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
// Minimum time
|
||||
const minTime = Math.floor(Date.now() / hour) - cacheExpiration;
|
||||
|
||||
// Load data from storage
|
||||
function load(key: keyof StorageConfig): void {
|
||||
const func = getGlobal(key);
|
||||
if (!func) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get one item from storage
|
||||
const getItem = (index: number): boolean => {
|
||||
const name = cachePrefix + index;
|
||||
const item = func.getItem(name);
|
||||
|
||||
if (typeof item !== 'string') {
|
||||
// Does not exist
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get item, validate it
|
||||
let valid = true;
|
||||
try {
|
||||
// Parse, check time stamp
|
||||
const data = JSON.parse(item as string) as StoredItem;
|
||||
if (
|
||||
typeof data !== 'object' ||
|
||||
typeof data.cached !== 'number' ||
|
||||
data.cached < minTime ||
|
||||
typeof data.data !== 'object' ||
|
||||
typeof data.data.prefix !== 'string'
|
||||
) {
|
||||
valid = false;
|
||||
} else {
|
||||
// Add icon set
|
||||
const prefix = data.data.prefix;
|
||||
const storage = getStorage(prefix);
|
||||
valid = addIconSet(storage, data.data) as boolean;
|
||||
}
|
||||
} catch (err) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
func.removeItem(name);
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
try {
|
||||
// Get version
|
||||
const version = func.getItem(versionKey);
|
||||
if (version !== cacheVersion) {
|
||||
if (version) {
|
||||
// Version is set, but invalid - remove old entries
|
||||
destroyCache(func);
|
||||
}
|
||||
// Empty data
|
||||
initCache(func, key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get number of stored items
|
||||
let total = getCount(func);
|
||||
for (let i = total - 1; i >= 0; i--) {
|
||||
if (!getItem(i)) {
|
||||
// Remove item
|
||||
if (i === total - 1) {
|
||||
// Last item - reduce country
|
||||
total--;
|
||||
} else {
|
||||
// Mark as empty
|
||||
emptyList[key].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update total
|
||||
setCount(func, key, total);
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in config) {
|
||||
load(key as keyof StorageConfig);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to cache icons
|
||||
*/
|
||||
export const storeCache: CacheIcons = (data: IconifyJSON): void => {
|
||||
if (!loaded) {
|
||||
loadCache();
|
||||
}
|
||||
|
||||
function store(key: keyof StorageConfig): boolean {
|
||||
if (!config[key]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const func = getGlobal(key);
|
||||
if (!func) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get item index
|
||||
let index = emptyList[key].shift();
|
||||
if (index === void 0) {
|
||||
// Create new index
|
||||
index = count[key];
|
||||
if (!setCount(func, key, index + 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and save item
|
||||
try {
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data,
|
||||
};
|
||||
func.setItem(cachePrefix + index, JSON.stringify(item));
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to store at localStorage first, then at sessionStorage
|
||||
if (!store('local')) {
|
||||
store('session');
|
||||
}
|
||||
};
|
30
packages/core/src/customisations/bool.ts
Normal file
30
packages/core/src/customisations/bool.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Get boolean customisation value from attribute
|
||||
*/
|
||||
export function toBoolean(
|
||||
name: string,
|
||||
value: unknown,
|
||||
defaultValue: boolean
|
||||
): boolean {
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
return value;
|
||||
|
||||
case 'number':
|
||||
return !!value;
|
||||
|
||||
case 'string':
|
||||
switch (value.toLowerCase()) {
|
||||
case '1':
|
||||
case 'true':
|
||||
case name:
|
||||
return true;
|
||||
|
||||
case '0':
|
||||
case 'false':
|
||||
case '':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
29
packages/core/src/customisations/compare.ts
Normal file
29
packages/core/src/customisations/compare.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { FullIconCustomisations, defaults } from '../customisations';
|
||||
|
||||
// Get all keys
|
||||
const allKeys: (keyof FullIconCustomisations)[] = Object.keys(
|
||||
defaults
|
||||
) as (keyof FullIconCustomisations)[];
|
||||
|
||||
// All keys without width/height
|
||||
const filteredKeys = allKeys.filter(key => key !== 'width' && key !== 'height');
|
||||
|
||||
/**
|
||||
* Compare sets of cusotmisations, return false if they are different, true if the same
|
||||
*
|
||||
* If dimensions are derived from props1 or props2, do not compare them.
|
||||
*/
|
||||
export function compare(
|
||||
item1: FullIconCustomisations,
|
||||
item2: FullIconCustomisations,
|
||||
compareDimensions = true
|
||||
): boolean {
|
||||
const keys = compareDimensions ? allKeys : filteredKeys;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (item1[key] !== item2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
67
packages/core/src/customisations/index.ts
Normal file
67
packages/core/src/customisations/index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { merge } from '../misc/merge';
|
||||
|
||||
/**
|
||||
* Icon alignment
|
||||
*/
|
||||
export type IconifyHorizontalIconAlignment = 'left' | 'center' | 'right';
|
||||
export type IconifyVerticalIconAlignment = 'top' | 'middle' | 'bottom';
|
||||
|
||||
/**
|
||||
* Icon size
|
||||
*/
|
||||
export type IconifyIconSize = null | string | number;
|
||||
|
||||
/**
|
||||
* Icon customisations
|
||||
*/
|
||||
export interface IconifyIconCustomisations {
|
||||
// Display mode
|
||||
inline?: boolean;
|
||||
|
||||
// Dimensions
|
||||
width?: IconifyIconSize;
|
||||
height?: IconifyIconSize;
|
||||
|
||||
// Alignment
|
||||
hAlign?: IconifyHorizontalIconAlignment;
|
||||
vAlign?: IconifyVerticalIconAlignment;
|
||||
slice?: boolean;
|
||||
|
||||
// Transformations
|
||||
hFlip?: boolean;
|
||||
vFlip?: boolean;
|
||||
rotate?: number;
|
||||
}
|
||||
|
||||
export type FullIconCustomisations = Required<IconifyIconCustomisations>;
|
||||
|
||||
/**
|
||||
* Default icon customisations values
|
||||
*/
|
||||
export const defaults: FullIconCustomisations = Object.freeze({
|
||||
// Display mode
|
||||
inline: false,
|
||||
|
||||
// Dimensions
|
||||
width: null,
|
||||
height: null,
|
||||
|
||||
// Alignment
|
||||
hAlign: 'center',
|
||||
vAlign: 'middle',
|
||||
slice: false,
|
||||
|
||||
// Transformations
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert IconifyIconCustomisations to FullIconCustomisations
|
||||
*/
|
||||
export function fullCustomisations(
|
||||
item: IconifyIconCustomisations
|
||||
): FullIconCustomisations {
|
||||
return merge(defaults, item) as FullIconCustomisations;
|
||||
}
|
40
packages/core/src/customisations/rotate.ts
Normal file
40
packages/core/src/customisations/rotate.ts
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Get rotation value
|
||||
*/
|
||||
export function rotateFromString(value: string): number {
|
||||
const units = value.replace(/^-?[0-9.]*/, '');
|
||||
|
||||
function cleanup(value: number): number {
|
||||
while (value < 0) {
|
||||
value += 4;
|
||||
}
|
||||
return value % 4;
|
||||
}
|
||||
|
||||
if (units === '') {
|
||||
const num = parseInt(value);
|
||||
return isNaN(num) ? 0 : cleanup(num);
|
||||
} else if (units !== value) {
|
||||
let split = 0;
|
||||
switch (units) {
|
||||
case '%':
|
||||
// 25% -> 1, 50% -> 2, ...
|
||||
split = 25;
|
||||
break;
|
||||
|
||||
case 'deg':
|
||||
// 90deg -> 1, 180deg -> 2, ...
|
||||
split = 90;
|
||||
}
|
||||
if (split) {
|
||||
let num = parseFloat(value.slice(0, value.length - units.length));
|
||||
if (isNaN(num)) {
|
||||
return 0;
|
||||
}
|
||||
num = num / split;
|
||||
return num % 1 === 0 ? cleanup(num) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
67
packages/core/src/customisations/shorthand.ts
Normal file
67
packages/core/src/customisations/shorthand.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { IconifyIconCustomisations } from '../customisations';
|
||||
|
||||
const separator = /[\s,]+/;
|
||||
|
||||
/**
|
||||
* Additional shorthand customisations
|
||||
*/
|
||||
export interface ShorthandIconCustomisations {
|
||||
// Sets both hFlip and vFlip
|
||||
flip?: string;
|
||||
|
||||
// Sets hAlign, vAlign and slice
|
||||
align?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply "flip" string to icon customisations
|
||||
*/
|
||||
export function flipFromString(
|
||||
custom: IconifyIconCustomisations,
|
||||
flip: string
|
||||
): void {
|
||||
flip.split(separator).forEach(str => {
|
||||
const value = str.trim();
|
||||
switch (value) {
|
||||
case 'horizontal':
|
||||
custom.hFlip = true;
|
||||
break;
|
||||
|
||||
case 'vertical':
|
||||
custom.vFlip = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply "align" string to icon customisations
|
||||
*/
|
||||
export function alignmentFromString(
|
||||
custom: IconifyIconCustomisations,
|
||||
align: string
|
||||
): void {
|
||||
align.split(separator).forEach(str => {
|
||||
const value = str.trim();
|
||||
switch (value) {
|
||||
case 'left':
|
||||
case 'center':
|
||||
case 'right':
|
||||
custom.hAlign = value;
|
||||
break;
|
||||
|
||||
case 'top':
|
||||
case 'middle':
|
||||
case 'bottom':
|
||||
custom.vAlign = value;
|
||||
break;
|
||||
|
||||
case 'slice':
|
||||
custom.slice = true;
|
||||
break;
|
||||
|
||||
case 'meet':
|
||||
custom.slice = false;
|
||||
}
|
||||
});
|
||||
}
|
26
packages/core/src/icon/index.ts
Normal file
26
packages/core/src/icon/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { IconifyIcon } from '@iconify/types';
|
||||
import { merge } from '../misc/merge';
|
||||
|
||||
export { IconifyIcon };
|
||||
export type FullIconifyIcon = Required<IconifyIcon>;
|
||||
|
||||
/**
|
||||
* Default values for IconifyIcon properties
|
||||
*/
|
||||
export const iconDefaults: FullIconifyIcon = Object.freeze({
|
||||
body: '',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 16,
|
||||
height: 16,
|
||||
rotate: 0,
|
||||
vFlip: false,
|
||||
hFlip: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Create new icon with all properties
|
||||
*/
|
||||
export function fullIcon(icon: IconifyIcon): FullIconifyIcon {
|
||||
return merge(iconDefaults, icon) as FullIconifyIcon;
|
||||
}
|
37
packages/core/src/icon/list.ts
Normal file
37
packages/core/src/icon/list.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { IconifyIconName, stringToIcon, validateIcon } from './name';
|
||||
|
||||
/**
|
||||
* Convert icons list from string/icon mix to icons and validate them
|
||||
*/
|
||||
export function listToIcons(
|
||||
list: (string | IconifyIconName)[],
|
||||
validate = true
|
||||
): IconifyIconName[] {
|
||||
const result: IconifyIconName[] = [];
|
||||
|
||||
list.forEach(item => {
|
||||
const icon: IconifyIconName =
|
||||
typeof item === 'string'
|
||||
? (stringToIcon(item) as IconifyIconName)
|
||||
: item;
|
||||
if (!validate || validateIcon(icon)) {
|
||||
result.push({
|
||||
prefix: icon.prefix,
|
||||
name: icon.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all prefixes
|
||||
*/
|
||||
export function getPrefixes(list: IconifyIconName[]): string[] {
|
||||
const prefixes: Record<string, boolean> = Object.create(null);
|
||||
list.forEach(icon => {
|
||||
prefixes[icon.prefix] = true;
|
||||
});
|
||||
return Object.keys(prefixes);
|
||||
}
|
47
packages/core/src/icon/merge.ts
Normal file
47
packages/core/src/icon/merge.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { IconifyIcon, iconDefaults } from './';
|
||||
|
||||
/**
|
||||
* Icon keys
|
||||
*/
|
||||
const iconKeys = Object.keys(iconDefaults) as (keyof IconifyIcon)[];
|
||||
|
||||
/**
|
||||
* Merge two icons
|
||||
*
|
||||
* icon2 overrides icon1
|
||||
*/
|
||||
export function mergeIcons(
|
||||
icon1: IconifyIcon,
|
||||
icon2: IconifyIcon
|
||||
): IconifyIcon {
|
||||
const icon = Object.create(null);
|
||||
iconKeys.forEach(key => {
|
||||
if (icon1[key] === void 0) {
|
||||
if (icon2[key] !== void 0) {
|
||||
icon[key] = icon2[key];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (icon2[key] === void 0) {
|
||||
icon[key] = icon1[key];
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'rotate':
|
||||
icon[key] =
|
||||
((icon1[key] as number) + (icon2[key] as number)) % 4;
|
||||
return;
|
||||
|
||||
case 'hFlip':
|
||||
case 'vFlip':
|
||||
icon[key] = icon1[key] !== icon2[key];
|
||||
return;
|
||||
|
||||
default:
|
||||
icon[key] = icon2[key];
|
||||
}
|
||||
});
|
||||
|
||||
return icon;
|
||||
}
|
53
packages/core/src/icon/name.ts
Normal file
53
packages/core/src/icon/name.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Icon name
|
||||
*/
|
||||
export interface IconifyIconName {
|
||||
readonly prefix: string;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expression to test part of icon name.
|
||||
*/
|
||||
const match = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||
|
||||
/**
|
||||
* Convert string to Icon object.
|
||||
*/
|
||||
export const stringToIcon = (value: string): IconifyIconName | null => {
|
||||
// Attempt to split by colon: "prefix:name"
|
||||
const colonSeparated = value.split(':');
|
||||
if (colonSeparated.length > 2) {
|
||||
return null;
|
||||
}
|
||||
if (colonSeparated.length === 2) {
|
||||
return {
|
||||
prefix: colonSeparated[0],
|
||||
name: colonSeparated[1],
|
||||
};
|
||||
}
|
||||
|
||||
// Attempt to split by dash: "prefix-name"
|
||||
const dashSeparated = value.split('-');
|
||||
if (dashSeparated.length > 1) {
|
||||
return {
|
||||
prefix: dashSeparated.shift() as string,
|
||||
name: dashSeparated.join('-'),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if icon is valid.
|
||||
*
|
||||
* This function is not part of stringToIcon because validation is not needed for most code.
|
||||
*/
|
||||
export const validateIcon = (icon: IconifyIconName | null): boolean => {
|
||||
if (!icon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!(icon.prefix.match(match) && icon.name.match(match));
|
||||
};
|
69
packages/core/src/icon/sort.ts
Normal file
69
packages/core/src/icon/sort.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { getStorage, IconStorage } from '../storage';
|
||||
import { IconifyIconName } from './name';
|
||||
|
||||
/**
|
||||
* Sorted icons list
|
||||
*/
|
||||
export interface SortedIcons {
|
||||
loaded: IconifyIconName[];
|
||||
missing: IconifyIconName[];
|
||||
pending: IconifyIconName[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if icons have been loaded
|
||||
*/
|
||||
export function sortIcons(icons: IconifyIconName[]): SortedIcons {
|
||||
const result: SortedIcons = {
|
||||
loaded: [],
|
||||
missing: [],
|
||||
pending: [],
|
||||
};
|
||||
const storage: Record<string, IconStorage> = Object.create(null);
|
||||
|
||||
// Sort icons alphabetically to prevent duplicates and make sure they are sorted in API queries
|
||||
icons.sort((a, b) => {
|
||||
if (a.prefix === b.prefix) {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
return a.prefix.localeCompare(b.prefix);
|
||||
});
|
||||
|
||||
let lastIcon: IconifyIconName = {
|
||||
prefix: '',
|
||||
name: '',
|
||||
};
|
||||
icons.forEach(icon => {
|
||||
if (lastIcon.prefix === icon.prefix && lastIcon.name === icon.name) {
|
||||
return;
|
||||
}
|
||||
lastIcon = icon;
|
||||
|
||||
// Check icon
|
||||
const prefix = icon.prefix;
|
||||
const name = icon.name;
|
||||
|
||||
if (storage[prefix] === void 0) {
|
||||
storage[prefix] = getStorage(prefix);
|
||||
}
|
||||
|
||||
const localStorage = storage[prefix];
|
||||
|
||||
let list;
|
||||
if (localStorage.icons[name] !== void 0) {
|
||||
list = result.loaded;
|
||||
} else if (localStorage.missing[name] !== void 0) {
|
||||
list = result.missing;
|
||||
} else {
|
||||
list = result.pending;
|
||||
}
|
||||
|
||||
const item: IconifyIconName = {
|
||||
prefix,
|
||||
name,
|
||||
};
|
||||
list.push(item);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
14
packages/core/src/interfaces/api.ts
Normal file
14
packages/core/src/interfaces/api.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { IconifyLoadIcons } from './loader';
|
||||
|
||||
/**
|
||||
* Function to check if icon is pending
|
||||
*/
|
||||
export type IsPending = (prefix: string, name: string) => boolean;
|
||||
|
||||
/**
|
||||
* API interface
|
||||
*/
|
||||
export interface IconifyAPI {
|
||||
isPending: IsPending;
|
||||
loadIcons: IconifyLoadIcons;
|
||||
}
|
11
packages/core/src/interfaces/cache.ts
Normal file
11
packages/core/src/interfaces/cache.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
|
||||
/**
|
||||
* Function to cache loaded icons set
|
||||
*/
|
||||
export type CacheIcons = (data: IconifyJSON) => void;
|
||||
|
||||
/**
|
||||
* Function to load icons from cache
|
||||
*/
|
||||
export type LoadIconsCache = () => void;
|
26
packages/core/src/interfaces/loader.ts
Normal file
26
packages/core/src/interfaces/loader.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { IconifyIconName } from '../icon/name';
|
||||
|
||||
/**
|
||||
* Function to abort loading (usually just removes callback because loading is already in progress)
|
||||
*/
|
||||
export type IconifyIconLoaderAbort = () => void;
|
||||
|
||||
/**
|
||||
* Loader callback
|
||||
*
|
||||
* Provides list of icons that have been loaded
|
||||
*/
|
||||
export type IconifyIconLoaderCallback = (
|
||||
loaded: IconifyIconName[],
|
||||
missing: IconifyIconName[],
|
||||
pending: IconifyIconName[],
|
||||
unsubscribe: IconifyIconLoaderAbort
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Function to load icons
|
||||
*/
|
||||
export type IconifyLoadIcons = (
|
||||
icons: (IconifyIconName | string)[],
|
||||
callback?: IconifyIconLoaderCallback
|
||||
) => IconifyIconLoaderAbort;
|
22
packages/core/src/misc/merge.ts
Normal file
22
packages/core/src/misc/merge.ts
Normal file
@ -0,0 +1,22 @@
|
||||
type MergeObject = Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Merge two objects
|
||||
*
|
||||
* Replacement for Object.assign() that is not supported by IE, so it cannot be used in production yet.
|
||||
*/
|
||||
export function merge<T>(item1: T, item2?: T, item3?: T): T {
|
||||
const result: MergeObject = Object.create(null);
|
||||
const items = [item1, item2, item3];
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const item = items[i];
|
||||
if (typeof item === 'object' && item) {
|
||||
for (const key in item) {
|
||||
result[key] = (item as MergeObject)[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result as T;
|
||||
}
|
18
packages/core/src/modules.ts
Normal file
18
packages/core/src/modules.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { CacheIcons } from './interfaces/cache';
|
||||
import { IconifyAPI } from './interfaces/api';
|
||||
|
||||
/**
|
||||
* Dynamic modules.
|
||||
*
|
||||
* Used as storage for optional functions that may or may not exist.
|
||||
* Each module must be set after including correct function for it, see build files as examples.
|
||||
*/
|
||||
interface Modules {
|
||||
// API module
|
||||
api?: IconifyAPI;
|
||||
|
||||
// Cache module (only function that stores cache. loading cache should be done when assigning module)
|
||||
cache?: CacheIcons;
|
||||
}
|
||||
|
||||
export const coreModules: Modules = {};
|
219
packages/core/src/storage/index.ts
Normal file
219
packages/core/src/storage/index.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import {
|
||||
IconifyJSON,
|
||||
IconifyIcon,
|
||||
IconifyOptional,
|
||||
IconifyIcons,
|
||||
IconifyAliases,
|
||||
IconifyAlias,
|
||||
} from '@iconify/types';
|
||||
import { FullIconifyIcon, iconDefaults, fullIcon } from '../icon';
|
||||
import { mergeIcons } from '../icon/merge';
|
||||
import { merge } from '../misc/merge';
|
||||
|
||||
/**
|
||||
* Get list of defaults keys
|
||||
*/
|
||||
const defaultsKeys = Object.keys(iconDefaults) as (keyof IconifyOptional)[];
|
||||
|
||||
/**
|
||||
* List of icons
|
||||
*/
|
||||
type IconRecords = Record<string, FullIconifyIcon | null>;
|
||||
|
||||
/**
|
||||
* Storage type
|
||||
*/
|
||||
export interface IconStorage {
|
||||
prefix: string;
|
||||
icons: IconRecords;
|
||||
missing: Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage by prefix
|
||||
*/
|
||||
const storage: Record<string, IconStorage> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Create new storage
|
||||
*/
|
||||
export function newStorage(prefix: string): IconStorage {
|
||||
return {
|
||||
prefix,
|
||||
icons: Object.create(null),
|
||||
missing: Object.create(null),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage for prefix
|
||||
*/
|
||||
export function getStorage(prefix: string): IconStorage {
|
||||
if (storage[prefix] === void 0) {
|
||||
storage[prefix] = newStorage(prefix);
|
||||
}
|
||||
return storage[prefix];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all prefixes
|
||||
*/
|
||||
export function listStoredPrefixes(): string[] {
|
||||
return Object.keys(storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve alias
|
||||
*/
|
||||
function resolveAlias(
|
||||
alias: IconifyAlias,
|
||||
icons: IconifyIcons,
|
||||
aliases: IconifyAliases,
|
||||
level = 0
|
||||
): IconifyIcon | null {
|
||||
const parent = alias.parent;
|
||||
if (icons[parent] !== void 0) {
|
||||
return mergeIcons(icons[parent], (alias as unknown) as IconifyIcon);
|
||||
}
|
||||
if (aliases[parent] !== void 0) {
|
||||
if (level > 2) {
|
||||
// icon + alias + alias + alias = too much nesting, possibly infinite
|
||||
throw new Error('Invalid alias');
|
||||
}
|
||||
const icon = resolveAlias(aliases[parent], icons, aliases, level + 1);
|
||||
if (icon) {
|
||||
return mergeIcons(icon, (alias as unknown) as IconifyIcon);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* What to track when adding icon set:
|
||||
*
|
||||
* none - do not track anything, return true on success
|
||||
* added - track added icons, return list of added icons on success
|
||||
* all - track added and missing icons, return full list on success
|
||||
*/
|
||||
export type AddIconSetTracking = 'none' | 'added' | 'all';
|
||||
|
||||
/**
|
||||
* Add icon set to storage
|
||||
*
|
||||
* Returns array of added icons if 'list' is true and icons were added successfully
|
||||
*/
|
||||
export function addIconSet(
|
||||
storage: IconStorage,
|
||||
data: IconifyJSON,
|
||||
list: AddIconSetTracking = 'none'
|
||||
): boolean | string[] {
|
||||
const added: string[] = [];
|
||||
|
||||
try {
|
||||
// Must be an object
|
||||
if (typeof data !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for missing icons list returned by API
|
||||
if (data.not_found instanceof Array) {
|
||||
const t = Date.now();
|
||||
data.not_found.forEach(name => {
|
||||
storage.missing[name] = t;
|
||||
if (list === 'all') {
|
||||
added.push(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Must have 'icons' object
|
||||
if (typeof data.icons !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get default values
|
||||
const defaults = Object.create(null);
|
||||
defaultsKeys.forEach(key => {
|
||||
if (data[key] !== void 0 && typeof data[key] !== 'object') {
|
||||
defaults[key] = data[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Get icons
|
||||
const icons = data.icons;
|
||||
Object.keys(icons).forEach(name => {
|
||||
const icon = icons[name];
|
||||
if (typeof icon.body !== 'string') {
|
||||
throw new Error('Invalid icon');
|
||||
}
|
||||
|
||||
// Freeze icon to make sure it will not be modified
|
||||
storage.icons[name] = Object.freeze(
|
||||
merge(iconDefaults, defaults, icon)
|
||||
);
|
||||
if (list !== 'none') {
|
||||
added.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
// Get aliases
|
||||
if (typeof data.aliases === 'object') {
|
||||
const aliases = data.aliases;
|
||||
Object.keys(aliases).forEach(name => {
|
||||
const icon = resolveAlias(aliases[name], icons, aliases, 1);
|
||||
if (icon) {
|
||||
// Freeze icon to make sure it will not be modified
|
||||
storage.icons[name] = Object.freeze(
|
||||
merge(iconDefaults, defaults, icon)
|
||||
);
|
||||
if (list !== 'none') {
|
||||
added.push(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list === 'none' ? true : added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add icon to storage
|
||||
*/
|
||||
export function addIcon(
|
||||
storage: IconStorage,
|
||||
name: string,
|
||||
icon: IconifyIcon
|
||||
): boolean {
|
||||
try {
|
||||
if (typeof icon.body === 'string') {
|
||||
// Freeze icon to make sure it will not be modified
|
||||
storage.icons[name] = Object.freeze(fullIcon(icon));
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if icon exists
|
||||
*/
|
||||
export function iconExists(storage: IconStorage, name: string): boolean {
|
||||
return storage.icons[name] !== void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon data
|
||||
*/
|
||||
export function getIcon(
|
||||
storage: IconStorage,
|
||||
name: string
|
||||
): Readonly<FullIconifyIcon> | null {
|
||||
const value = storage.icons[name];
|
||||
return value === void 0 ? null : value;
|
||||
}
|
7
packages/core/src/tsconfig.json
Normal file
7
packages/core/src/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../lib",
|
||||
"rootDir": "."
|
||||
}
|
||||
}
|
60
packages/core/tests/10-basic/calc-size-test.ts
Normal file
60
packages/core/tests/10-basic/calc-size-test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { calcSize } from '../../lib/builder/calc-size';
|
||||
|
||||
describe('Testing calcSize', () => {
|
||||
it('Simple size', () => {
|
||||
const width = 36;
|
||||
const height = 48;
|
||||
|
||||
// Get width from height and height from width
|
||||
expect(calcSize('48', width / height)).to.be.equal('36');
|
||||
expect(calcSize('36', height / width)).to.be.equal('48');
|
||||
|
||||
expect(calcSize(48, width / height)).to.be.equal(36);
|
||||
expect(calcSize(36, height / width)).to.be.equal(48);
|
||||
});
|
||||
|
||||
it('Numbers', () => {
|
||||
const width = 36;
|
||||
const height = 48;
|
||||
|
||||
// Simple numbers
|
||||
expect(calcSize(24, width / height)).to.be.equal(18);
|
||||
expect(calcSize(30, width / height)).to.be.equal(22.5);
|
||||
expect(calcSize(99, width / height)).to.be.equal(74.25);
|
||||
|
||||
// Rounding numbers
|
||||
expect(calcSize(100 / 3, height / width)).to.be.equal(44.45);
|
||||
expect(calcSize(11.1111111, width / height)).to.be.equal(8.34);
|
||||
expect(calcSize(11.1111111, width / height, 1000)).to.be.equal(8.334);
|
||||
});
|
||||
|
||||
it('Strings', () => {
|
||||
const width = 36;
|
||||
const height = 48;
|
||||
|
||||
// Simple units
|
||||
expect(calcSize('48px', width / height)).to.be.equal('36px');
|
||||
expect(calcSize('24%', width / height)).to.be.equal('18%');
|
||||
expect(calcSize('1em', width / height)).to.be.equal('0.75em');
|
||||
|
||||
// Add space
|
||||
expect(calcSize('24 Pixels', width / height)).to.be.equal('18 Pixels');
|
||||
|
||||
// Multiple sets of numbers
|
||||
expect(calcSize('48% + 5em', width / height)).to.be.equal(
|
||||
'36% + 3.75em'
|
||||
);
|
||||
expect(calcSize('calc(1em + 8px)', height / width)).to.be.equal(
|
||||
'calc(1.34em + 10.67px)'
|
||||
);
|
||||
expect(calcSize('-webkit-calc(1em + 8px)', width / height)).to.be.equal(
|
||||
'-webkit-calc(0.75em + 6px)'
|
||||
);
|
||||
|
||||
// Invalid strings
|
||||
expect(calcSize('-.', width / height)).to.be.equal('-.');
|
||||
expect(calcSize('@foo', width / height)).to.be.equal('@foo');
|
||||
});
|
||||
});
|
79
packages/core/tests/10-basic/icon-name-test.ts
Normal file
79
packages/core/tests/10-basic/icon-name-test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
stringToIcon,
|
||||
validateIcon,
|
||||
IconifyIconName,
|
||||
} from '../../lib/icon/name';
|
||||
|
||||
describe('Testing icon name', () => {
|
||||
it('Converting and validating', () => {
|
||||
let icon;
|
||||
|
||||
// Simple prefix-name
|
||||
icon = stringToIcon('fa-home') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'fa',
|
||||
name: 'home',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(true);
|
||||
|
||||
// Simple prefix:name
|
||||
icon = stringToIcon('fa:arrow-left') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'fa',
|
||||
name: 'arrow-left',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(true);
|
||||
|
||||
// Longer prefix:name
|
||||
icon = stringToIcon('mdi-light:home-outline') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'mdi-light',
|
||||
name: 'home-outline',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(true);
|
||||
|
||||
// Underscore is not an acceptable separator
|
||||
icon = stringToIcon('fa_home');
|
||||
expect(icon).to.be.eql(null);
|
||||
expect(validateIcon(icon)).to.be.equal(false);
|
||||
|
||||
// Invalid character '_': fail validateIcon
|
||||
icon = stringToIcon('fa:home_outline') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'fa',
|
||||
name: 'home_outline',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(false);
|
||||
|
||||
// Too many colons: fail stringToIcon
|
||||
icon = stringToIcon('mdi-light:home:outline');
|
||||
expect(icon).to.be.eql(null);
|
||||
expect(validateIcon(icon)).to.be.equal(false);
|
||||
|
||||
// Upper case: fail validateIcon
|
||||
icon = stringToIcon('MD:Home') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'MD',
|
||||
name: 'Home',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(false);
|
||||
|
||||
// Numbers: pass
|
||||
icon = stringToIcon('1:foo') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: '1',
|
||||
name: 'foo',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(true);
|
||||
|
||||
// Accented letters: fail validateIcon
|
||||
icon = stringToIcon('md-fõö') as IconifyIconName;
|
||||
expect(icon).to.be.eql({
|
||||
prefix: 'md',
|
||||
name: 'fõö',
|
||||
});
|
||||
expect(validateIcon(icon)).to.be.equal(false);
|
||||
});
|
||||
});
|
403
packages/core/tests/10-basic/storage-test.ts
Normal file
403
packages/core/tests/10-basic/storage-test.ts
Normal file
@ -0,0 +1,403 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-ignore */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
newStorage,
|
||||
addIcon,
|
||||
iconExists,
|
||||
getIcon,
|
||||
addIconSet,
|
||||
} from '../../lib/storage';
|
||||
import { FullIconifyIcon, IconifyIcon } from '../../lib/icon';
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
|
||||
describe('Testing storage', () => {
|
||||
it('Adding icon', () => {
|
||||
const storage = newStorage('foo');
|
||||
|
||||
// Add one icon
|
||||
addIcon(storage, 'test', {
|
||||
body: '<path d="" />',
|
||||
width: 20,
|
||||
height: 16,
|
||||
});
|
||||
|
||||
// Add another icon with reserved keyword as name
|
||||
addIcon(storage, 'constructor', {
|
||||
body: '<g></g>',
|
||||
width: 24,
|
||||
height: 24,
|
||||
rotate: 1,
|
||||
});
|
||||
|
||||
// Add invalid icon
|
||||
addIcon(storage, 'invalid', ({} as unknown) as IconifyIcon);
|
||||
|
||||
// Should not include 'invalid'
|
||||
expect(Object.keys(storage.icons)).to.be.eql(['test', 'constructor']);
|
||||
|
||||
// Test iconExists
|
||||
expect(iconExists(storage, 'test')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'constructor')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'invalid')).to.be.equal(false);
|
||||
expect(iconExists(storage, 'missing')).to.be.equal(false);
|
||||
|
||||
// Test getIcon
|
||||
let expected: FullIconifyIcon = {
|
||||
body: '<path d="" />',
|
||||
width: 20,
|
||||
height: 16,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
const icon = getIcon(storage, 'test');
|
||||
expect(icon).to.be.eql(expected);
|
||||
expected = {
|
||||
body: '<g></g>',
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 1,
|
||||
};
|
||||
|
||||
// Test icon mutation
|
||||
let thrown = false;
|
||||
try {
|
||||
// @ts-ignore
|
||||
icon.width = 12;
|
||||
} catch (err) {
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).to.be.equal(true);
|
||||
|
||||
expect(getIcon(storage, 'constructor')).to.be.eql(expected);
|
||||
expect(getIcon(storage, 'invalid')).to.be.equal(null);
|
||||
expect(getIcon(storage, 'missing')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Adding simple icon set', () => {
|
||||
const storage = newStorage('foo');
|
||||
|
||||
// Add two icons
|
||||
expect(
|
||||
addIconSet(storage, {
|
||||
prefix: 'foo',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
},
|
||||
},
|
||||
height: 24,
|
||||
})
|
||||
).to.be.equal(true);
|
||||
|
||||
expect(Object.keys(storage.icons)).to.be.eql(['icon1', 'icon2']);
|
||||
|
||||
// Test iconExists
|
||||
expect(iconExists(storage, 'icon1')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'icon2')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'invalid')).to.be.equal(false);
|
||||
expect(iconExists(storage, 'missing')).to.be.equal(false);
|
||||
|
||||
// Test getIcon
|
||||
let expected: FullIconifyIcon = {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(getIcon(storage, 'icon1')).to.be.eql(expected);
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(getIcon(storage, 'icon2')).to.be.eql(expected);
|
||||
expect(getIcon(storage, 'invalid')).to.be.equal(null);
|
||||
expect(getIcon(storage, 'missing')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Icon set with invalid default values', () => {
|
||||
const storage = newStorage('foo');
|
||||
|
||||
// Missing prefix, invalid default values
|
||||
expect(
|
||||
addIconSet(storage, ({
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
// Default should not override this
|
||||
height: 20,
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
},
|
||||
icon3: {
|
||||
// Missing 'body'
|
||||
width: 24,
|
||||
},
|
||||
},
|
||||
height: 24,
|
||||
// Objects should be ignored. Not testing other types because validation is done only for objects
|
||||
rotate: {
|
||||
foo: 1,
|
||||
},
|
||||
hFlip: null,
|
||||
} as unknown) as IconifyJSON)
|
||||
// Should return false because of exception, but still add icon1 and icon2 before failing on icon3
|
||||
).to.be.equal(false);
|
||||
|
||||
expect(Object.keys(storage.icons)).to.be.eql(['icon1', 'icon2']);
|
||||
|
||||
// Test iconExists
|
||||
expect(iconExists(storage, 'icon1')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'icon2')).to.be.equal(true);
|
||||
expect(iconExists(storage, 'invalid')).to.be.equal(false);
|
||||
expect(iconExists(storage, 'missing')).to.be.equal(false);
|
||||
|
||||
// Test getIcon
|
||||
let expected: FullIconifyIcon = {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(getIcon(storage, 'icon1')).to.be.eql(expected);
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(getIcon(storage, 'icon2')).to.be.eql(expected);
|
||||
expect(getIcon(storage, 'invalid')).to.be.equal(null);
|
||||
expect(getIcon(storage, 'missing')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Icon set with simple aliases', () => {
|
||||
const storage = newStorage('foo');
|
||||
|
||||
expect(
|
||||
addIconSet(storage, {
|
||||
prefix: 'foo',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
},
|
||||
},
|
||||
aliases: {
|
||||
alias1: {
|
||||
parent: 'icon1',
|
||||
},
|
||||
alias2: {
|
||||
parent: 'icon2',
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
vFlip: true,
|
||||
},
|
||||
alias3: {
|
||||
parent: 'icon3',
|
||||
},
|
||||
},
|
||||
height: 24,
|
||||
})
|
||||
).to.be.equal(true);
|
||||
|
||||
expect(Object.keys(storage.icons)).to.be.eql([
|
||||
'icon1',
|
||||
'icon2',
|
||||
'alias1',
|
||||
'alias2',
|
||||
]);
|
||||
|
||||
// Test getIcon
|
||||
let expected: FullIconifyIcon = {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(getIcon(storage, 'alias1')).to.be.eql(expected);
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: true,
|
||||
rotate: 2,
|
||||
};
|
||||
expect(getIcon(storage, 'alias2')).to.be.eql(expected);
|
||||
expect(getIcon(storage, 'alias3')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('Icon set with nested aliases', () => {
|
||||
const storage = newStorage('foo');
|
||||
|
||||
expect(
|
||||
addIconSet(storage, {
|
||||
prefix: 'foo',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="icon1" />',
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
},
|
||||
},
|
||||
aliases: {
|
||||
alias2a: {
|
||||
// Alias before parent
|
||||
parent: 'alias2f',
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
alias2f: {
|
||||
parent: 'icon2',
|
||||
width: 22,
|
||||
rotate: 1,
|
||||
hFlip: true,
|
||||
vFlip: true,
|
||||
},
|
||||
alias2z: {
|
||||
// Alias after parent
|
||||
parent: 'alias2f',
|
||||
width: 21,
|
||||
rotate: 3,
|
||||
},
|
||||
alias2z3: {
|
||||
// 3 parents: alias2z, alias2f, icon2
|
||||
parent: 'alias2z',
|
||||
},
|
||||
alias2z4: {
|
||||
// 4 parents: alias2z3, alias2z, alias2f, icon2
|
||||
parent: 'alias2z3',
|
||||
},
|
||||
},
|
||||
height: 24,
|
||||
})
|
||||
// Should have thrown exception on 'alias2z4'
|
||||
).to.be.equal(false);
|
||||
|
||||
expect(Object.keys(storage.icons)).to.be.eql([
|
||||
'icon1',
|
||||
'icon2',
|
||||
'alias2a',
|
||||
'alias2f',
|
||||
'alias2z',
|
||||
'alias2z3',
|
||||
]);
|
||||
|
||||
// Test icon
|
||||
let expected: FullIconifyIcon = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: true,
|
||||
vFlip: false,
|
||||
rotate: 1,
|
||||
};
|
||||
expect(getIcon(storage, 'icon2')).to.be.eql(expected);
|
||||
|
||||
// Test simple alias
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 22,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: true,
|
||||
rotate: 2,
|
||||
};
|
||||
expect(getIcon(storage, 'alias2f')).to.be.eql(expected);
|
||||
|
||||
// Test nested aliases
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 21,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: true,
|
||||
rotate: 1, // 5
|
||||
};
|
||||
expect(getIcon(storage, 'alias2z')).to.be.eql(expected);
|
||||
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 20,
|
||||
height: 20,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: true,
|
||||
rotate: 2,
|
||||
};
|
||||
expect(getIcon(storage, 'alias2a')).to.be.eql(expected);
|
||||
|
||||
// 3 levels
|
||||
expected = {
|
||||
body: '<path d="icon2" />',
|
||||
width: 21,
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
hFlip: false,
|
||||
vFlip: true,
|
||||
rotate: 1, // 5
|
||||
};
|
||||
expect(getIcon(storage, 'alias2z3')).to.be.eql(expected);
|
||||
});
|
||||
});
|
203
packages/core/tests/20-builder/icon-to-svg-test.ts
Normal file
203
packages/core/tests/20-builder/icon-to-svg-test.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { iconToSVG, IconifyIconBuildResult } from '../../lib/builder';
|
||||
import { FullIconifyIcon, iconDefaults, fullIcon } from '../../lib/icon';
|
||||
import {
|
||||
FullIconCustomisations,
|
||||
defaults,
|
||||
fullCustomisations,
|
||||
} from '../../lib/customisations';
|
||||
|
||||
describe('Testing iconToSVG', () => {
|
||||
it('Empty icon', () => {
|
||||
const custom: FullIconCustomisations = defaults;
|
||||
const icon: FullIconifyIcon = iconDefaults;
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 16 16',
|
||||
},
|
||||
body: '',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Auto size, inline, body', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
inline: true,
|
||||
height: 'auto',
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
body: '<path d="" />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '16',
|
||||
height: '16',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 16 16',
|
||||
},
|
||||
body: '<path d="" />',
|
||||
inline: true,
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Auto size, inline, body', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
inline: true,
|
||||
height: 'auto',
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
body: '<path d="" />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '16',
|
||||
height: '16',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 16 16',
|
||||
},
|
||||
body: '<path d="" />',
|
||||
inline: true,
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Custom size, alignment', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
height: 'auto',
|
||||
hAlign: 'left',
|
||||
slice: true,
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
width: 20,
|
||||
height: 16,
|
||||
body: '<path d="..." />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '20',
|
||||
height: '16',
|
||||
preserveAspectRatio: 'xMinYMid slice',
|
||||
viewBox: '0 0 20 16',
|
||||
},
|
||||
body: '<path d="..." />',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Rotation, alignment', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
height: '40px',
|
||||
vAlign: 'bottom',
|
||||
rotate: 1,
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
width: 20,
|
||||
height: 16,
|
||||
body: '<path d="..." />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '32px',
|
||||
height: '40px',
|
||||
preserveAspectRatio: 'xMidYMax meet',
|
||||
viewBox: '0 0 16 20',
|
||||
},
|
||||
body: '<g transform="rotate(90 8 8)"><path d="..." /></g>',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Flip, alignment', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
height: '32',
|
||||
vAlign: 'top',
|
||||
hAlign: 'right',
|
||||
hFlip: true,
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
width: 20,
|
||||
height: 16,
|
||||
body: '<path d="..." />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '40',
|
||||
height: '32',
|
||||
preserveAspectRatio: 'xMaxYMin meet',
|
||||
viewBox: '0 0 20 16',
|
||||
},
|
||||
body:
|
||||
'<g transform="translate(20 0) scale(-1 1)"><path d="..." /></g>',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Flip, rotation', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
vFlip: true,
|
||||
rotate: 1,
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
width: 20,
|
||||
height: 16,
|
||||
body: '<path d="..." />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '0.8em',
|
||||
height: '1em',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 16 20',
|
||||
},
|
||||
body:
|
||||
'<g transform="rotate(90 8 8) translate(0 16) scale(1 -1)"><path d="..." /></g>',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
|
||||
it('Flip and rotation canceling eachother', () => {
|
||||
const custom: FullIconCustomisations = fullCustomisations({
|
||||
width: '1em',
|
||||
height: 'auto',
|
||||
hFlip: true,
|
||||
vFlip: true,
|
||||
rotate: 2,
|
||||
});
|
||||
const icon: FullIconifyIcon = fullIcon({
|
||||
width: 20,
|
||||
height: 16,
|
||||
body: '<path d="..." />',
|
||||
});
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '1em',
|
||||
height: '16',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 20 16',
|
||||
},
|
||||
body: '<path d="..." />',
|
||||
};
|
||||
|
||||
const result = iconToSVG(icon, custom);
|
||||
expect(result).to.be.eql(expected);
|
||||
});
|
||||
});
|
369
packages/core/tests/30-api/10-callbacks-test.ts
Normal file
369
packages/core/tests/30-api/10-callbacks-test.ts
Normal file
@ -0,0 +1,369 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
callbacks,
|
||||
updateCallbacks,
|
||||
storeCallback,
|
||||
} from '../../lib/api/callbacks';
|
||||
import { sortIcons } from '../../lib/icon/sort';
|
||||
import { getStorage, addIconSet } from '../../lib/storage';
|
||||
|
||||
describe('Testing API callbacks', () => {
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
prefixCounter++;
|
||||
return 'api-cb-test-' + (prefixCounter < 10 ? '0' : '') + prefixCounter;
|
||||
}
|
||||
|
||||
it('Simple callback', done => {
|
||||
const prefix = nextPrefix();
|
||||
let counter = 0;
|
||||
|
||||
const storage = getStorage(prefix);
|
||||
const abort = storeCallback(
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
expect(unsubscribe).to.be.equal(abort);
|
||||
|
||||
counter++;
|
||||
switch (counter) {
|
||||
case 1:
|
||||
// First run - icon1 should be loaded, icon3 should be missing
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]);
|
||||
expect(pending).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(callbacks[prefix].length).to.be.equal(1);
|
||||
|
||||
// Add icon2 and trigger update
|
||||
addIconSet(storage, {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
icon2: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
updateCallbacks(prefix);
|
||||
return;
|
||||
|
||||
case 2:
|
||||
// Second run - icon2 should be added, completing callback
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]);
|
||||
expect(pending).to.be.eql([]);
|
||||
expect(callbacks[prefix].length).to.be.equal(0);
|
||||
done();
|
||||
}
|
||||
},
|
||||
sortIcons([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]),
|
||||
[prefix]
|
||||
);
|
||||
|
||||
// Test callbacks
|
||||
expect(callbacks[prefix].length).to.be.equal(1);
|
||||
|
||||
// Test update - should do nothing
|
||||
updateCallbacks(prefix);
|
||||
|
||||
// Wait for tick because updateCallbacks will use one
|
||||
setTimeout(() => {
|
||||
// Callback should not have been called yet
|
||||
expect(counter).to.be.equal(0);
|
||||
|
||||
// Add few icons and run updateCallbacks
|
||||
addIconSet(storage, {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
not_found: ['icon3'],
|
||||
});
|
||||
updateCallbacks(prefix);
|
||||
});
|
||||
});
|
||||
|
||||
it('Callback that should not be stored', () => {
|
||||
const prefix = nextPrefix();
|
||||
|
||||
const storage = getStorage(prefix);
|
||||
addIconSet(storage, {
|
||||
prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
not_found: ['icon3'],
|
||||
});
|
||||
|
||||
storeCallback(
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
throw new Error('This code should not be executed!');
|
||||
},
|
||||
sortIcons([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]),
|
||||
[prefix]
|
||||
);
|
||||
|
||||
// callbacks should not have been initialised
|
||||
expect(callbacks[prefix]).to.be.equal(void 0);
|
||||
});
|
||||
|
||||
it('Cancel callback', done => {
|
||||
const prefix = nextPrefix();
|
||||
let counter = 0;
|
||||
|
||||
const storage = getStorage(prefix);
|
||||
const abort = storeCallback(
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
expect(unsubscribe).to.be.equal(abort);
|
||||
|
||||
counter++;
|
||||
expect(counter).to.be.equal(1);
|
||||
|
||||
// First run - icon1 should be loaded, icon3 should be missing
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]);
|
||||
expect(pending).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(callbacks[prefix].length).to.be.equal(1);
|
||||
|
||||
// Add icon2 and trigger update
|
||||
addIconSet(storage, {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
icon2: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
updateCallbacks(prefix);
|
||||
|
||||
// Unsubscribe and set timer to call done()
|
||||
unsubscribe();
|
||||
expect(callbacks[prefix].length).to.be.equal(0);
|
||||
setTimeout(done);
|
||||
},
|
||||
sortIcons([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
]),
|
||||
[prefix]
|
||||
);
|
||||
|
||||
// Test callbacks
|
||||
expect(callbacks[prefix].length).to.be.equal(1);
|
||||
|
||||
// Test update - should do nothing
|
||||
updateCallbacks(prefix);
|
||||
|
||||
// Wait for tick because updateCallbacks will use one
|
||||
setTimeout(() => {
|
||||
// Callback should not have been called yet
|
||||
expect(counter).to.be.equal(0);
|
||||
|
||||
// Add few icons and run updateCallbacks
|
||||
addIconSet(storage, {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
not_found: ['icon3'],
|
||||
});
|
||||
updateCallbacks(prefix);
|
||||
});
|
||||
});
|
||||
|
||||
it('Multiple prefixes', done => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
let counter = 0;
|
||||
|
||||
const storage1 = getStorage(prefix1);
|
||||
const storage2 = getStorage(prefix2);
|
||||
|
||||
const abort = storeCallback(
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
expect(unsubscribe).to.be.equal(abort);
|
||||
|
||||
counter++;
|
||||
switch (counter) {
|
||||
case 1:
|
||||
// First run - icon1 should be loaded, icon3 should be missing
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix: prefix1,
|
||||
name: 'icon1',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([
|
||||
{
|
||||
prefix: prefix1,
|
||||
name: 'icon3',
|
||||
},
|
||||
]);
|
||||
expect(pending).to.be.eql([
|
||||
{
|
||||
prefix: prefix2,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(callbacks[prefix1].length).to.be.equal(0);
|
||||
expect(callbacks[prefix2].length).to.be.equal(1);
|
||||
|
||||
// Add icon2 and trigger update
|
||||
addIconSet(storage2, {
|
||||
prefix: prefix2,
|
||||
icons: {
|
||||
icon2: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
updateCallbacks(prefix2);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Second run - icon2 should be loaded
|
||||
expect(callbacks[prefix1].length).to.be.equal(0);
|
||||
expect(callbacks[prefix2].length).to.be.equal(0);
|
||||
done();
|
||||
break;
|
||||
|
||||
default:
|
||||
done('Callback was called ' + counter + ' times.');
|
||||
}
|
||||
},
|
||||
sortIcons([
|
||||
{
|
||||
prefix: prefix1,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix: prefix2,
|
||||
name: 'icon2',
|
||||
},
|
||||
{
|
||||
prefix: prefix1,
|
||||
name: 'icon3',
|
||||
},
|
||||
]),
|
||||
[prefix1, prefix2]
|
||||
);
|
||||
|
||||
// Test callbacks
|
||||
expect(callbacks[prefix1].length).to.be.equal(1);
|
||||
expect(callbacks[prefix2].length).to.be.equal(1);
|
||||
|
||||
// Test update - should do nothing
|
||||
updateCallbacks(prefix1);
|
||||
|
||||
// Wait for tick because updateCallbacks will use one
|
||||
setTimeout(() => {
|
||||
// Callback should not have been called yet
|
||||
expect(counter).to.be.equal(0);
|
||||
|
||||
// Add few icons and run updateCallbacks
|
||||
addIconSet(storage1, {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
not_found: ['icon3'],
|
||||
});
|
||||
updateCallbacks(prefix1);
|
||||
});
|
||||
});
|
||||
});
|
85
packages/core/tests/30-api/10-modules-test.ts
Normal file
85
packages/core/tests/30-api/10-modules-test.ts
Normal file
@ -0,0 +1,85 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { RedundancyPendingItem } from '@cyberalien/redundancy';
|
||||
import {
|
||||
setAPIConfig,
|
||||
getAPIConfig,
|
||||
IconifyAPIConfig,
|
||||
} from '../../lib/api/config';
|
||||
import {
|
||||
setAPIModule,
|
||||
APIQueryParams,
|
||||
getAPIModule,
|
||||
IconifyAPIModule,
|
||||
} from '../../lib/api/modules';
|
||||
|
||||
describe('Testing API modules', () => {
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
prefixCounter++;
|
||||
return (
|
||||
'api-mod-test-' + (prefixCounter < 10 ? '0' : '') + prefixCounter
|
||||
);
|
||||
}
|
||||
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons,
|
||||
};
|
||||
return [item];
|
||||
};
|
||||
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
throw new Error('Unexpected API call');
|
||||
};
|
||||
|
||||
it('Empty module', () => {
|
||||
const prefix = nextPrefix();
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://localhost:3000'],
|
||||
maxURL: 500,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Set fake module
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Get config
|
||||
const config = getAPIConfig(prefix) as IconifyAPIConfig;
|
||||
expect(config).to.not.be.equal(null);
|
||||
|
||||
// Check setAPIConfig
|
||||
expect(config.resources).to.be.eql(['https://localhost:3000']);
|
||||
|
||||
// Check getAPIModule()
|
||||
const item = getAPIModule(prefix) as IconifyAPIModule;
|
||||
expect(item).to.not.be.equal(null);
|
||||
expect(item.prepare).to.be.equal(prepareQuery);
|
||||
expect(item.send).to.be.equal(sendQuery);
|
||||
|
||||
// Get module for different prefix to make sure it is empty
|
||||
const prefix2 = nextPrefix();
|
||||
const item2 = getAPIModule(prefix2);
|
||||
expect(item2).to.be.equal(null);
|
||||
});
|
||||
});
|
637
packages/core/tests/30-api/20-loading-test.ts
Normal file
637
packages/core/tests/30-api/20-loading-test.ts
Normal file
@ -0,0 +1,637 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { RedundancyPendingItem } from '@cyberalien/redundancy';
|
||||
import { setAPIConfig } from '../../lib/api/config';
|
||||
import { setAPIModule, APIQueryParams } from '../../lib/api/modules';
|
||||
import { API } from '../../lib/api/';
|
||||
|
||||
describe('Testing API loadIcons', () => {
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
prefixCounter++;
|
||||
return (
|
||||
'api-load-test-' + (prefixCounter < 10 ? '0' : '') + prefixCounter
|
||||
);
|
||||
}
|
||||
|
||||
it('Loading few icons', done => {
|
||||
const prefix = nextPrefix();
|
||||
let asyncCounter = 0;
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Icon loader
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons,
|
||||
};
|
||||
|
||||
// This callback should be called first
|
||||
expect(asyncCounter).to.be.equal(1);
|
||||
asyncCounter++;
|
||||
|
||||
// Test input and return as one item
|
||||
const expected: APIQueryParams = {
|
||||
prefix,
|
||||
icons: ['icon1', 'icon2'],
|
||||
};
|
||||
expect(item).to.be.eql(expected);
|
||||
|
||||
return [item];
|
||||
};
|
||||
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
// This callback should be called after prepareQuery
|
||||
expect(asyncCounter).to.be.equal(2);
|
||||
asyncCounter++;
|
||||
|
||||
// Test input
|
||||
expect(host).to.be.equal('https://api1.local');
|
||||
const expected: APIQueryParams = {
|
||||
prefix,
|
||||
icons: ['icon1', 'icon2'],
|
||||
};
|
||||
expect(params).to.be.eql(expected);
|
||||
|
||||
// Send data
|
||||
status.done({
|
||||
prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Counter should not have increased after status.done() call becuse parsing result should be done on next tick
|
||||
expect(asyncCounter).to.be.equal(3);
|
||||
};
|
||||
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Load icons
|
||||
API.loadIcons(
|
||||
[
|
||||
// as icon
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
// as string
|
||||
prefix + ':icon2',
|
||||
],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// This callback should be called last
|
||||
expect(asyncCounter).to.be.equal(3);
|
||||
asyncCounter++;
|
||||
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
expect(API.isPending(prefix, 'icon1')).to.be.equal(false);
|
||||
expect(API.isPending(prefix, 'icon3')).to.be.equal(false);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
// Test isPending
|
||||
expect(API.isPending(prefix, 'icon1')).to.be.equal(true);
|
||||
expect(API.isPending(prefix, 'icon3')).to.be.equal(false);
|
||||
|
||||
// Make sure asyncCounter wasn't increased because loading shoud happen on next tick
|
||||
expect(asyncCounter).to.be.equal(0);
|
||||
asyncCounter++;
|
||||
});
|
||||
|
||||
it('Split results', done => {
|
||||
const prefix = nextPrefix();
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Icon loader
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
// Split all icons in multiple queries, one icon per query
|
||||
const results: APIQueryParams[] = [];
|
||||
icons.forEach(icon => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons: [icon],
|
||||
};
|
||||
results.push(item);
|
||||
});
|
||||
|
||||
expect(results.length).to.be.equal(2);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
let queryCounter = 0;
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
// Test input
|
||||
expect(host).to.be.equal('https://api1.local');
|
||||
|
||||
// Icon names should match queryCounter: 'icon1' on first run, 'icon2' on second run
|
||||
queryCounter++;
|
||||
const expected: APIQueryParams = {
|
||||
prefix,
|
||||
icons: ['icon' + queryCounter],
|
||||
};
|
||||
expect(params).to.be.eql(expected);
|
||||
|
||||
// Send only requested icons
|
||||
const icons = Object.create(null);
|
||||
params.icons.forEach(icon => {
|
||||
icons[icon] = {
|
||||
body: '<path d="" />',
|
||||
};
|
||||
});
|
||||
status.done({
|
||||
prefix,
|
||||
icons,
|
||||
});
|
||||
};
|
||||
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Load icons
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix + ':icon1', prefix + ':icon2'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once because results should be sent in same tick
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Fail on default host', done => {
|
||||
const prefix = nextPrefix();
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
rotate: 100, // 100ms to speed up test
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Icon loader
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons,
|
||||
};
|
||||
return [item];
|
||||
};
|
||||
|
||||
let queryCounter = 0;
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
queryCounter++;
|
||||
switch (queryCounter) {
|
||||
case 1:
|
||||
// First call on api1
|
||||
expect(host).to.be.equal('https://api1.local');
|
||||
|
||||
// Do nothing - fake failed response
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// First call on api2
|
||||
expect(host).to.be.equal('https://api2.local');
|
||||
|
||||
// Return result
|
||||
status.done({
|
||||
prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
done(
|
||||
`Unexpected additional call to sendQuery for host ${host}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Load icons
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix + ':icon1', prefix + ':icon2'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Fail on default host, multiple queries', done => {
|
||||
const prefix = nextPrefix();
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
rotate: 100, // 100ms to speed up test
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Icon loader
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons,
|
||||
};
|
||||
return [item];
|
||||
};
|
||||
|
||||
let queryCounter = 0;
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
queryCounter++;
|
||||
switch (queryCounter) {
|
||||
case 1:
|
||||
// First call on api1
|
||||
expect(params.icons).to.be.eql(['icon1', 'icon2']);
|
||||
expect(host).to.be.equal('https://api1.local');
|
||||
|
||||
// Do nothing - fake failed response
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// First call on api2
|
||||
expect(params.icons).to.be.eql(['icon1', 'icon2']);
|
||||
expect(host).to.be.equal('https://api2.local');
|
||||
|
||||
// Return result
|
||||
status.done({
|
||||
prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Second call, should have api2 as default
|
||||
expect(params.icons).to.be.eql(['icon3', 'icon4']);
|
||||
expect(host).to.be.equal('https://api2.local');
|
||||
|
||||
// Return result
|
||||
status.done({
|
||||
prefix,
|
||||
icons: {
|
||||
icon3: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon4: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
done(
|
||||
`Unexpected additional call to sendQuery for host ${host}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
prefix
|
||||
);
|
||||
|
||||
// Load icons
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix + ':icon1', prefix + ':icon2'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
// Send another query on next tick
|
||||
setTimeout(() => {
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix + ':icon3', prefix + ':icon4'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon3',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon4',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Fail on default host, multiple queries with different prefixes', done => {
|
||||
const prefix = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set config
|
||||
setAPIConfig(
|
||||
{
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
rotate: 100, // 100ms to speed up test
|
||||
},
|
||||
[prefix, prefix2]
|
||||
);
|
||||
|
||||
// Icon loader
|
||||
const prepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const item: APIQueryParams = {
|
||||
prefix,
|
||||
icons,
|
||||
};
|
||||
return [item];
|
||||
};
|
||||
|
||||
let queryCounter = 0;
|
||||
const sendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
queryCounter++;
|
||||
switch (queryCounter) {
|
||||
case 1:
|
||||
// First call on api1
|
||||
expect(params.prefix).to.be.equal(prefix);
|
||||
expect(params.icons).to.be.eql(['icon1', 'icon2']);
|
||||
expect(host).to.be.equal('https://api1.local');
|
||||
|
||||
// Do nothing - fake failed response
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// First call on api2
|
||||
expect(params.prefix).to.be.equal(prefix);
|
||||
expect(params.icons).to.be.eql(['icon1', 'icon2']);
|
||||
expect(host).to.be.equal('https://api2.local');
|
||||
|
||||
// Return result
|
||||
status.done({
|
||||
prefix: params.prefix,
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Second call, should have api2 as default
|
||||
expect(params.prefix).to.be.equal(prefix2);
|
||||
expect(params.icons).to.be.eql(['icon2', 'icon4']);
|
||||
expect(host).to.be.equal('https://api2.local');
|
||||
|
||||
// Return result
|
||||
status.done({
|
||||
prefix: params.prefix,
|
||||
icons: {
|
||||
icon2: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
icon4: {
|
||||
body: '<path d="" />',
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
done(
|
||||
`Unexpected additional call to sendQuery for host ${host}.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
setAPIModule(
|
||||
{
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
},
|
||||
[prefix, prefix2]
|
||||
);
|
||||
|
||||
// Load icons
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix + ':icon1', prefix + ':icon2'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
// Send another query on next tick for different prefix that shares configuration
|
||||
setTimeout(() => {
|
||||
let callbackCalled = false;
|
||||
API.loadIcons(
|
||||
[prefix2 + ':icon2', prefix2 + ':icon4'],
|
||||
(loaded, missing, pending, unsubscribe) => {
|
||||
// Callback should be called only once
|
||||
expect(callbackCalled).to.be.equal(false);
|
||||
callbackCalled = true;
|
||||
|
||||
// Test data
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
prefix: prefix2,
|
||||
name: 'icon2',
|
||||
},
|
||||
{
|
||||
prefix: prefix2,
|
||||
name: 'icon4',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
expect(pending).to.be.eql([]);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
219
packages/core/tests/30-cache/10-basic-test.ts
Normal file
219
packages/core/tests/30-cache/10-basic-test.ts
Normal file
@ -0,0 +1,219 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { count, config, loadCache } from '../../lib/cache/storage';
|
||||
import {
|
||||
nextPrefix,
|
||||
createCache,
|
||||
reset,
|
||||
cachePrefix,
|
||||
cacheVersion,
|
||||
versionKey,
|
||||
countKey,
|
||||
} from './fake_cache';
|
||||
|
||||
describe('Testing mocked localStorage', () => {
|
||||
it('No usable cache', () => {
|
||||
reset({});
|
||||
|
||||
// Config before tests
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
|
||||
// Attempt to load
|
||||
loadCache();
|
||||
|
||||
// Everything should be disabled
|
||||
expect(config).to.be.eql({
|
||||
local: false,
|
||||
session: false,
|
||||
});
|
||||
|
||||
// Nothing should have loaded
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('Empty localStorage', () => {
|
||||
reset({
|
||||
localStorage: createCache(),
|
||||
});
|
||||
|
||||
// Config before tests
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
|
||||
// Attempt to load
|
||||
loadCache();
|
||||
|
||||
// sessionStorage should be disabled
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
|
||||
// Nothing should have loaded
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('Restricted localStorage', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one item
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '1');
|
||||
cache.setItem(
|
||||
cachePrefix + '0',
|
||||
JSON.stringify({
|
||||
cached: Date.now(),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Prevent reading and writing
|
||||
cache.canRead = false;
|
||||
cache.canWrite = false;
|
||||
|
||||
// Set cache and test it
|
||||
reset({
|
||||
localStorage: cache,
|
||||
sessionStorage: cache,
|
||||
});
|
||||
|
||||
// Config before tests
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
|
||||
// Attempt to load
|
||||
loadCache();
|
||||
|
||||
// Everything should be disabled because read-only mock throws errors
|
||||
expect(config).to.be.eql({
|
||||
local: false,
|
||||
session: false,
|
||||
});
|
||||
|
||||
// Nothing should have loaded
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('localStorage with one item', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '1');
|
||||
cache.setItem(
|
||||
cachePrefix + '0',
|
||||
JSON.stringify({
|
||||
cached: Date.now(),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Set cache and test it
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Config before tests
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
|
||||
// Attempt to load
|
||||
loadCache();
|
||||
|
||||
// sessionStorage should be disabled
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
|
||||
// One item should be in localStorage
|
||||
expect(count).to.be.eql({
|
||||
local: 1,
|
||||
session: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('localStorage and sessionStorage', () => {
|
||||
reset({
|
||||
localStorage: createCache(),
|
||||
sessionStorage: createCache(),
|
||||
});
|
||||
|
||||
// Config before tests
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
|
||||
// Attempt to load
|
||||
loadCache();
|
||||
|
||||
// Everything should be working
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
|
||||
// Empty storage
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
});
|
||||
});
|
435
packages/core/tests/30-cache/20-loading-test.ts
Normal file
435
packages/core/tests/30-cache/20-loading-test.ts
Normal file
@ -0,0 +1,435 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
loadCache,
|
||||
count,
|
||||
config,
|
||||
emptyList,
|
||||
StoredItem,
|
||||
} from '../../lib/cache/storage';
|
||||
import { getStorage, iconExists, getIcon } from '../../lib/storage';
|
||||
import {
|
||||
nextPrefix,
|
||||
createCache,
|
||||
reset,
|
||||
cachePrefix,
|
||||
cacheVersion,
|
||||
versionKey,
|
||||
countKey,
|
||||
hour,
|
||||
cacheExpiration,
|
||||
} from './fake_cache';
|
||||
import { IconifyIcon, IconifyJSON } from '@iconify/types';
|
||||
|
||||
describe('Testing loading from localStorage', () => {
|
||||
it('Valid icon set', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '1');
|
||||
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
cache.setItem(cachePrefix + '0', JSON.stringify(item));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icon should exist now
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(true);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 1,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Expired icon set', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '1');
|
||||
|
||||
const item: StoredItem = {
|
||||
// Expiration date
|
||||
cached: Math.floor(Date.now() / hour) - cacheExpiration - 1,
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
cache.setItem(cachePrefix + '0', JSON.stringify(item));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icon should not have loaded
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Bad icon set', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '1');
|
||||
cache.setItem(
|
||||
cachePrefix + '0',
|
||||
JSON.stringify({
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
// Missing 'body' property
|
||||
width: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icon should not have loaded
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Wrong counter', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '0'); // Should be at least "1"
|
||||
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
cache.setItem(cachePrefix + '0', JSON.stringify(item));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icon should not have loaded
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Missing entries at the end', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '5');
|
||||
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
cache.setItem(cachePrefix + '0', JSON.stringify(item));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icon should exist now
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(true);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 1,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Missing entries', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add two icon sets
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '5');
|
||||
|
||||
// Missing: 0, 2, 3
|
||||
const item1: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo1: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const item4: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo4: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
cache.setItem(cachePrefix + '1', JSON.stringify(item1));
|
||||
cache.setItem(cachePrefix + '4', JSON.stringify(item4));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo1')).to.be.equal(false);
|
||||
expect(iconExists(icons, 'foo4')).to.be.equal(false);
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icons should exist now
|
||||
expect(iconExists(icons, 'foo1')).to.be.equal(true);
|
||||
expect(iconExists(icons, 'foo4')).to.be.equal(true);
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 5,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [3, 2, 0], // reserse order
|
||||
session: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Using both storage options', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache1 = createCache();
|
||||
const cache2 = createCache();
|
||||
|
||||
// Add few icon sets
|
||||
cache1.setItem(versionKey, cacheVersion);
|
||||
cache2.setItem(versionKey, cacheVersion);
|
||||
|
||||
cache1.setItem(countKey, '6');
|
||||
cache2.setItem(countKey, '3');
|
||||
|
||||
// Create 5 items
|
||||
const icons: IconifyJSON[] = [];
|
||||
const items: StoredItem[] = [];
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['foo' + i]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
icons.push(icon);
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
// Add items 1,3,5 to localStorage
|
||||
[1, 3, 5].forEach((index) => {
|
||||
cache1.setItem(cachePrefix + index, JSON.stringify(items[index]));
|
||||
});
|
||||
|
||||
// Add items 0 and 2 to sessionStorage
|
||||
[0, 2].forEach((index) => {
|
||||
cache2.setItem(cachePrefix + index, JSON.stringify(items[index]));
|
||||
});
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache1,
|
||||
sessionStorage: cache2,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const iconsStorage = getStorage(prefix);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
|
||||
false,
|
||||
`Icon ${i} should not exist yet`
|
||||
);
|
||||
}
|
||||
|
||||
// Load localStorage
|
||||
loadCache();
|
||||
|
||||
// Icons should exist now, except for number 4
|
||||
for (let i = 0; i < 6; i++) {
|
||||
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
|
||||
i !== 4,
|
||||
`Icon ${i} failed loading test`
|
||||
);
|
||||
}
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 6,
|
||||
session: 3,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [4, 2, 0],
|
||||
session: [1],
|
||||
});
|
||||
});
|
||||
});
|
683
packages/core/tests/30-cache/30-saving-test.ts
Normal file
683
packages/core/tests/30-cache/30-saving-test.ts
Normal file
@ -0,0 +1,683 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import {
|
||||
loadCache,
|
||||
storeCache,
|
||||
count,
|
||||
config,
|
||||
emptyList,
|
||||
StoredItem,
|
||||
} from '../../lib/cache/storage';
|
||||
import { getStorage, iconExists } from '../../lib/storage';
|
||||
import {
|
||||
nextPrefix,
|
||||
createCache,
|
||||
reset,
|
||||
cachePrefix,
|
||||
cacheVersion,
|
||||
versionKey,
|
||||
countKey,
|
||||
hour,
|
||||
cacheExpiration,
|
||||
} from './fake_cache';
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
|
||||
describe('Testing saving to localStorage', () => {
|
||||
it('One icon set', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add one icon set
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Save item
|
||||
storeCache(icon);
|
||||
|
||||
// Storing in cache should not add item to storage
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Check data that should have been updated because storeCache()
|
||||
// should call load function before first execution
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 1,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache.getItem(cachePrefix + '0')).to.be.equal(
|
||||
JSON.stringify(item)
|
||||
);
|
||||
expect(cache.getItem(countKey)).to.be.equal('1');
|
||||
expect(cache.getItem(versionKey)).to.be.equal(cacheVersion);
|
||||
});
|
||||
|
||||
it('Multiple icon sets', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add icon sets
|
||||
const icon0: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo0: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item0: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon0,
|
||||
};
|
||||
const icon1: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item1: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon1,
|
||||
};
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Save items
|
||||
storeCache(icon0);
|
||||
storeCache(icon1);
|
||||
|
||||
// Check data that should have been updated because storeCache()
|
||||
// should call load function before first execution
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 2,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache.getItem(cachePrefix + '0')).to.be.equal(
|
||||
JSON.stringify(item0)
|
||||
);
|
||||
expect(cache.getItem(cachePrefix + '1')).to.be.equal(
|
||||
JSON.stringify(item1)
|
||||
);
|
||||
expect(cache.getItem(countKey)).to.be.equal('2');
|
||||
expect(cache.getItem(versionKey)).to.be.equal(cacheVersion);
|
||||
});
|
||||
|
||||
it('Adding icon set on unused spot', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add icon sets
|
||||
const icon0: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo0: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item0: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon0,
|
||||
};
|
||||
const icon1: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item1: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon1,
|
||||
};
|
||||
|
||||
// Add item
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '2');
|
||||
cache.setItem(cachePrefix + '1', JSON.stringify(item1));
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Load data
|
||||
loadCache();
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 2,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [0],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Save items
|
||||
storeCache(icon0);
|
||||
|
||||
// Check data
|
||||
expect(count).to.be.eql({
|
||||
local: 2,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache.getItem(cachePrefix + '0')).to.be.equal(
|
||||
JSON.stringify(item0)
|
||||
);
|
||||
expect(cache.getItem(cachePrefix + '1')).to.be.equal(
|
||||
JSON.stringify(item1)
|
||||
);
|
||||
expect(cache.getItem(countKey)).to.be.equal('2');
|
||||
expect(cache.getItem(versionKey)).to.be.equal(cacheVersion);
|
||||
});
|
||||
|
||||
it('Adding multiple icon sets to existing data', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add icon sets
|
||||
const icons: IconifyJSON[] = [];
|
||||
const items: StoredItem[] = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['foo' + i]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
|
||||
// Make items 2 and 4 expire
|
||||
if (i === 2 || i === 4) {
|
||||
item.cached -= cacheExpiration + 1;
|
||||
}
|
||||
|
||||
// Change expiration for items 6 and 8 to almost expire
|
||||
if (i === 6 || i === 8) {
|
||||
item.cached -= cacheExpiration - 1;
|
||||
}
|
||||
|
||||
icons.push(icon);
|
||||
items.push(item);
|
||||
|
||||
// Skip items 1, 5, 9+
|
||||
if (i !== 1 && i !== 5 && i < 9) {
|
||||
cache.setItem(cachePrefix + i, JSON.stringify(item));
|
||||
}
|
||||
}
|
||||
|
||||
cache.setItem(versionKey, cacheVersion);
|
||||
cache.setItem(countKey, '10');
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
sessionStorage: cache,
|
||||
});
|
||||
|
||||
// Load data
|
||||
loadCache();
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: false,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 9, // item 9 was missing
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
// mix of expired and skipped items
|
||||
// reverse order, 9 should not be there because it is last item
|
||||
session: [5, 4, 2, 1],
|
||||
});
|
||||
expect(cache.getItem(countKey)).to.be.equal('9');
|
||||
|
||||
// Check cached items
|
||||
[0, 3, 6, 7, 8].forEach((index) => {
|
||||
expect(cache.getItem(cachePrefix + index)).to.be.equal(
|
||||
JSON.stringify(items[index]),
|
||||
`Checking item ${index}`
|
||||
);
|
||||
});
|
||||
|
||||
// Check expired items - should have been deleted
|
||||
// Also check items that weren't supposed to be added
|
||||
[2, 4, 1, 5, 9, 10, 11, 12, 13].forEach((index) => {
|
||||
expect(cache.getItem(cachePrefix + index)).to.be.equal(
|
||||
null,
|
||||
`Checking item ${index}`
|
||||
);
|
||||
});
|
||||
|
||||
// Add item 5
|
||||
storeCache(icons[5]);
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 9,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [4, 2, 1],
|
||||
});
|
||||
expect(cache.getItem(countKey)).to.be.equal('9');
|
||||
|
||||
// Add items 4, 2, 1
|
||||
const list = [4, 2, 1];
|
||||
list.slice(0).forEach((index) => {
|
||||
expect(list.shift()).to.be.equal(index);
|
||||
storeCache(icons[index]);
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 9,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: list,
|
||||
});
|
||||
expect(cache.getItem(countKey)).to.be.equal('9');
|
||||
});
|
||||
|
||||
// Add item 10
|
||||
storeCache(icons[10]);
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 10,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
expect(cache.getItem(countKey)).to.be.equal('10');
|
||||
|
||||
// Add item 11
|
||||
storeCache(icons[11]);
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 11,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
expect(cache.getItem(countKey)).to.be.equal('11');
|
||||
});
|
||||
|
||||
it('Overwrite outdated data', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache = createCache();
|
||||
|
||||
// Add data in old format
|
||||
cache.setItem(versionKey, '1.0.6');
|
||||
cache.setItem(countKey, '3');
|
||||
for (let i = 0; i < 3; i++) {
|
||||
cache.setItem(
|
||||
cachePrefix + i,
|
||||
JSON.stringify({
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['foo' + i]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache,
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const icons = getStorage(prefix);
|
||||
expect(iconExists(icons, 'foo1')).to.be.equal(false);
|
||||
|
||||
// Load cache
|
||||
loadCache();
|
||||
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 0,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Add one icon set
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
foo: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
|
||||
// Save item
|
||||
storeCache(icon);
|
||||
|
||||
// Storing in cache should not add item to storage
|
||||
expect(iconExists(icons, 'foo')).to.be.equal(false);
|
||||
|
||||
// Check data that should have been updated because storeCache()
|
||||
// should call load function before first execution
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: false,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 1,
|
||||
session: 0,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache.getItem(cachePrefix + '0')).to.be.equal(
|
||||
JSON.stringify(item)
|
||||
);
|
||||
expect(cache.getItem(countKey)).to.be.equal('1');
|
||||
expect(cache.getItem(versionKey)).to.be.equal(cacheVersion);
|
||||
});
|
||||
|
||||
it('Using both storage options', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache1 = createCache();
|
||||
const cache2 = createCache();
|
||||
|
||||
// Add icon sets to localStorage
|
||||
cache1.setItem(versionKey, cacheVersion);
|
||||
cache1.setItem(countKey, '3');
|
||||
[0, 1, 2].forEach((index) => {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['foo' + index]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
cache1.setItem(cachePrefix + index, JSON.stringify(item));
|
||||
});
|
||||
|
||||
// Add icon sets to sessionStorage
|
||||
cache2.setItem(versionKey, cacheVersion);
|
||||
cache2.setItem(countKey, '4');
|
||||
[0, 1, 2, 3].forEach((index) => {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['bar' + index]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
cache2.setItem(cachePrefix + index, JSON.stringify(item));
|
||||
});
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache1,
|
||||
sessionStorage: cache2,
|
||||
});
|
||||
|
||||
// Load data
|
||||
loadCache();
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 3,
|
||||
session: 4,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const iconsStorage = getStorage(prefix);
|
||||
for (let i = 0; i < count.local; i++) {
|
||||
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
|
||||
true,
|
||||
`Icon foo${i} should have loaded`
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < count.session; i++) {
|
||||
expect(iconExists(iconsStorage, 'bar' + i)).to.be.equal(
|
||||
true,
|
||||
`Icon bar${i} should have loaded`
|
||||
);
|
||||
}
|
||||
|
||||
// Add new item to localStorage
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
'new-icon': {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
storeCache(icon);
|
||||
|
||||
// Check data
|
||||
expect(count).to.be.eql({
|
||||
local: 4, // +1
|
||||
session: 4,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache1.getItem(cachePrefix + '3')).to.be.equal(
|
||||
JSON.stringify(item)
|
||||
);
|
||||
});
|
||||
|
||||
it('Using both storage options, but localStorage is read only', () => {
|
||||
const prefix = nextPrefix();
|
||||
const cache1 = createCache();
|
||||
const cache2 = createCache();
|
||||
|
||||
// Add icon sets to localStorage
|
||||
cache1.setItem(versionKey, cacheVersion);
|
||||
cache1.setItem(countKey, '3');
|
||||
[0, 1, 2].forEach((index) => {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['foo' + index]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
cache1.setItem(cachePrefix + index, JSON.stringify(item));
|
||||
});
|
||||
|
||||
// Add icon sets to sessionStorage
|
||||
cache2.setItem(versionKey, cacheVersion);
|
||||
cache2.setItem(countKey, '4');
|
||||
[0, 1, 2, 3].forEach((index) => {
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
['bar' + index]: {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
cache2.setItem(cachePrefix + index, JSON.stringify(item));
|
||||
});
|
||||
|
||||
// Set cache
|
||||
reset({
|
||||
localStorage: cache1,
|
||||
sessionStorage: cache2,
|
||||
});
|
||||
|
||||
// Load data
|
||||
loadCache();
|
||||
|
||||
// Check data
|
||||
expect(config).to.be.eql({
|
||||
local: true,
|
||||
session: true,
|
||||
});
|
||||
expect(count).to.be.eql({
|
||||
local: 3,
|
||||
session: 4,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check icon storage
|
||||
const iconsStorage = getStorage(prefix);
|
||||
for (let i = 0; i < count.local; i++) {
|
||||
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
|
||||
true,
|
||||
`Icon foo${i} should have loaded`
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < count.session; i++) {
|
||||
expect(iconExists(iconsStorage, 'bar' + i)).to.be.equal(
|
||||
true,
|
||||
`Icon bar${i} should have loaded`
|
||||
);
|
||||
}
|
||||
|
||||
// Set localStorage to read-only
|
||||
cache1.canWrite = false;
|
||||
|
||||
// Add new item to localStorage
|
||||
const icon: IconifyJSON = {
|
||||
prefix: prefix,
|
||||
icons: {
|
||||
'new-icon': {
|
||||
body: '<g></g>',
|
||||
},
|
||||
},
|
||||
};
|
||||
const item: StoredItem = {
|
||||
cached: Math.floor(Date.now() / hour),
|
||||
data: icon,
|
||||
};
|
||||
storeCache(icon);
|
||||
|
||||
// Check data
|
||||
expect(count).to.be.eql({
|
||||
local: 3,
|
||||
session: 5,
|
||||
});
|
||||
expect(emptyList).to.be.eql({
|
||||
local: [],
|
||||
session: [],
|
||||
});
|
||||
|
||||
// Check cache
|
||||
expect(cache2.getItem(cachePrefix + '4')).to.be.equal(
|
||||
JSON.stringify(item)
|
||||
);
|
||||
});
|
||||
});
|
114
packages/core/tests/30-cache/fake_cache.ts
Normal file
114
packages/core/tests/30-cache/fake_cache.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { mock, count, config, emptyList } from '../../lib/cache/storage';
|
||||
|
||||
/**
|
||||
* Get next icon set prefix for testing
|
||||
*/
|
||||
let prefixCounter = 0;
|
||||
export function nextPrefix(): string {
|
||||
return 'fake-storage-' + prefixCounter++;
|
||||
}
|
||||
|
||||
// Cache version. Bump when structure changes
|
||||
export const cacheVersion = 'iconify1';
|
||||
|
||||
// Cache keys
|
||||
export const cachePrefix = 'iconify';
|
||||
export const countKey = cachePrefix + '-count';
|
||||
export const versionKey = cachePrefix + '-version';
|
||||
|
||||
/**
|
||||
* Cache expiration
|
||||
*/
|
||||
export const hour = 3600000;
|
||||
export const cacheExpiration = 168; // In hours
|
||||
|
||||
/**
|
||||
* Storage class
|
||||
*/
|
||||
export class Storage {
|
||||
canRead = true;
|
||||
canWrite = true;
|
||||
items: Record<string, string> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Get number of items
|
||||
*/
|
||||
get length(): number {
|
||||
if (!this.canRead) {
|
||||
throw new Error('Restricted storage');
|
||||
}
|
||||
return Object.keys(this.items).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
getItem(name: string): string | null {
|
||||
if (!this.canRead) {
|
||||
throw new Error('Restricted storage');
|
||||
}
|
||||
return this.items[name] === void 0 ? null : this.items[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
setItem(name: string, value: string): void {
|
||||
if (!this.canWrite) {
|
||||
throw new Error('Read-only storage');
|
||||
}
|
||||
this.items[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
removeItem(name: string): void {
|
||||
if (!this.canWrite) {
|
||||
throw new Error('Read-only storage');
|
||||
}
|
||||
delete this.items[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear everything
|
||||
*/
|
||||
clear(): void {
|
||||
if (!this.canWrite) {
|
||||
throw new Error('Read-only storage');
|
||||
}
|
||||
this.items = Object.create(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create fake storage, assign localStorage type
|
||||
*/
|
||||
export function createCache(): typeof localStorage {
|
||||
return (new Storage() as unknown) as typeof localStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset test
|
||||
*
|
||||
* @param fakeWindow
|
||||
*/
|
||||
export function reset(fakeWindow: Record<string, typeof localStorage>): void {
|
||||
// Replace window
|
||||
mock(fakeWindow);
|
||||
|
||||
// Reset all data
|
||||
for (const key in config) {
|
||||
const attr = (key as unknown) as keyof typeof config;
|
||||
config[attr] = true;
|
||||
count[attr] = 0;
|
||||
emptyList[attr] = [];
|
||||
}
|
||||
}
|
8
packages/core/tests/tsconfig.json
Normal file
8
packages/core/tests/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "mocha"],
|
||||
"outDir": "../tests-compiled",
|
||||
"rootDir": "."
|
||||
}
|
||||
}
|
14
packages/core/tsconfig-base.json
Normal file
14
packages/core/tsconfig-base.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
12
packages/core/tsconfig.json
Normal file
12
packages/core/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./src/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "./tests/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
4
packages/iconify/.gitignore
vendored
Normal file
4
packages/iconify/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
lib
|
324
packages/iconify/README.md
Normal file
324
packages/iconify/README.md
Normal file
@ -0,0 +1,324 @@
|
||||
# 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/).
|
||||
|
||||
# Iconify SVG framework
|
||||
|
||||
There are many fonts and SVG sets available, but they all have one thing in common: using any font or SVG set limits you to icons that are included in that set and forces browsers to load entire font or icons set. That limits developers to one or two fonts or icon sets.
|
||||
|
||||
Iconify uses a new innovative approach to loading icons. Unlike fonts and SVG frameworks, Iconify only loads icons that are used on the page instead of loading entire fonts. How is it done? By serving icons dynamically from publicly available JSON API (you can make a copy of script and API if you prefer to keep everything on your servers).
|
||||
|
||||
Iconify SVG framework is designed to be as easy to use as possible.
|
||||
|
||||
Add this line to your page to load Iconify SVG framework (you can add it to `<head>` section of page or before `</body>`):
|
||||
|
||||
```html
|
||||
<script src="https://code.iconify.design/2/2.0.0-dev/iconify.min.js"></script>
|
||||
```
|
||||
|
||||
or, if you are building a project with something like WebPack or Rollup, you can include the script by installing `@iconify/iconify` as a dependency and importing it in your project:
|
||||
|
||||
```js
|
||||
import Iconify from '@iconify/iconify';
|
||||
```
|
||||
|
||||
To add any icon, write something like this:
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="eva:people-outline"></span>
|
||||
```
|
||||
|
||||
![Sample](https://iconify.design/assets/images/eva-people-outline.svg)
|
||||
|
||||
or this:
|
||||
|
||||
```html
|
||||
<span class="iconify-inline" data-icon="fa-solid:home"></span>
|
||||
<a href="#">Return home!</a>
|
||||
```
|
||||
|
||||
![Screenshot](https://iconify.design/assets/images/inline-sample.png)
|
||||
|
||||
That is it. Change `data-icon` value to the name of the icon you want to use. There are over 50,000 premade icons to choose from, including FontAwesome, Material Design Icons, Entypo+, Box Icons, Unicons and even several emoji sets.
|
||||
|
||||
Do you want to make your own icon sets? Tools for making custom icon sets are available on GitHub. See documentation.
|
||||
|
||||
## How does it work?
|
||||
|
||||
The syntax is similar to icon fonts. Instead of inserting `SVG` in the document, you write a placeholder element, such `SPAN` or `I`.
|
||||
|
||||
Iconify SVG framework finds those placeholders and uses the following logic to parse them:
|
||||
|
||||
1. Retrieves icon name from `data-icon` attribute.
|
||||
2. Checks if icon exists. If not, it sends a request to Iconify API to retrieve icon data.
|
||||
3. Replaces placeholder element with `SVG`.
|
||||
|
||||
This is done in a fraction of a second. Iconify SVG framework watches DOM for changes, so whenever you add new placeholders, it immediately replaces them with `SVG`, making it easy to use with dynamic content, such as AJAX forms.
|
||||
|
||||
### Inline mode
|
||||
|
||||
Code examples above use different class names: the first example uses "iconify", the second example uses "iconify-inline".
|
||||
|
||||
What is the difference?
|
||||
|
||||
- "iconify" renders icon as is, so it behaves like an image.
|
||||
- "iconify-inline" renders adds vertical alignment to the icon, making it behave like text (inline mode).
|
||||
|
||||
Usually, icon fonts do not render like normal images, they render like text. Text is aligned slightly below the baseline.
|
||||
|
||||
Visual example to show the difference between inline and block modes:
|
||||
|
||||
![Inline icon](https://iconify.design/assets/images/inline.png)
|
||||
|
||||
Why is the inline mode needed?
|
||||
|
||||
- To easily align icons within the text, such as emojis.
|
||||
- To make the transition from outdated icon fonts to SVG easier.
|
||||
|
||||
Use "iconify" for decorations, use "iconify-inline" if you want the icon to behave like an icon font.
|
||||
|
||||
#### data-inline attribute
|
||||
|
||||
In addition to using "iconify-inline" class, you can toggle inline mode with the `data-inline` attribute.
|
||||
|
||||
Set value to "true" to force inline mode, set value to "false" to use block mode.
|
||||
|
||||
Different ways to use block mode:
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="eva:people-outline"></span>
|
||||
<span class="iconify" data-icon="eva:people-outline" data-inline="false"></span>
|
||||
```
|
||||
|
||||
Different ways to use inline mode:
|
||||
|
||||
```html
|
||||
<span class="iconify-inline" data-icon="eva:people-outline"></span>
|
||||
<span class="iconify" data-icon="eva:people-outline" data-inline="true"></span>
|
||||
<span
|
||||
class="iconify"
|
||||
data-icon="eva:people-outline"
|
||||
style="vertical-align: -0.125em"
|
||||
></span>
|
||||
```
|
||||
|
||||
## Iconify API
|
||||
|
||||
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 SVG framework does 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. How is it done? By serving icons dynamically from publicly available JSON API.
|
||||
|
||||
### Custom API
|
||||
|
||||
Relying on a third party service is often not an option. Many companies and developers prefer to keep everything on their own servers to have full control.
|
||||
|
||||
Iconify API and icon sets are all [available on GitHub](https://github.com/iconify), making it easy to host API on your own server.
|
||||
|
||||
For more details see [Iconify API documentation](https://iconify.design/docs/api-hosting/).
|
||||
|
||||
You can also create custom Iconify API to serve your own icons. For more details see [hosting custom icons in Iconify documentation](https://iconify.design/docs/api-custom-hosting/).
|
||||
|
||||
### Using Iconify offline
|
||||
|
||||
While the default method of retrieving icons is to retrieve them from API, there are other options. Iconify SVG framework is designed to be as flexible as possible.
|
||||
|
||||
Easiest option to serve icons without API is by creating icon bundles.
|
||||
|
||||
Icon bundles are small scripts that you can load after Iconify SVG framework or bundle it together in one file.
|
||||
|
||||
For more details see [icon bundles in Iconify documentation](https://iconify.design/docs/icon-bundles/).
|
||||
|
||||
Another option is to import icons and bundle them with Iconify, similar to React and Vue components. Example:
|
||||
|
||||
```js
|
||||
// Installation: npm install --save-dev @iconify/iconify
|
||||
import Iconify from '@iconify/iconify';
|
||||
// Installation: npm install --save-dev @iconify/icons-dashicons
|
||||
import adminUsers from '@iconify/icons-dashicons/admin-users';
|
||||
|
||||
// Unlike React and Vue components, in SVG framework each icon added with addIcon() name must have a
|
||||
// prefix and a name. In this example prefix is "dashicons" and name is "admin-users".
|
||||
Iconify.addIcon('dashicons:admin-users', adminUsers);
|
||||
```
|
||||
|
||||
```html
|
||||
<span class="iconify" data-icon="dashicons:admin-users"></span>
|
||||
```
|
||||
|
||||
See [Iconify for React](http://github.com/iconify/iconify/packages/react) documentation for more details.
|
||||
|
||||
## Color
|
||||
|
||||
There are 2 types of icons: monotone and coloured.
|
||||
|
||||
- Monotone icons are icons that use only 1 colour and you can change that colour. Most icon sets fall into this category: FontAwesome, Unicons, Material Design Icons, etc.
|
||||
- Coloured icons are icons that use the preset palette. Most emoji icons fall into this category: Noto Emoji, Emoji One, etc. You cannot change the palette for those icons.
|
||||
|
||||
Monotone icons use font colour, just like glyph fonts. To change colour, you can do this:
|
||||
|
||||
```html
|
||||
<span class="iconify icon-bell" data-icon="vaadin-bell"></span>
|
||||
```
|
||||
|
||||
and add this to CSS:
|
||||
|
||||
```css
|
||||
.icon-bell {
|
||||
color: #f80;
|
||||
}
|
||||
.icon-bell:hover {
|
||||
color: #f00;
|
||||
}
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-color.png)
|
||||
|
||||
## Dimensions
|
||||
|
||||
By default all icons are scaled to 1em height. To control icon height use font-size:
|
||||
|
||||
```html
|
||||
<span class="iconify icon-clipboard" data-icon="emojione-clipboard"></span>
|
||||
```
|
||||
|
||||
and add this to css:
|
||||
|
||||
```css
|
||||
.icon-clipboard {
|
||||
font-size: 32px;
|
||||
}
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-size.png)
|
||||
|
||||
you might also need to set line-height:
|
||||
|
||||
```css
|
||||
.icon-clipboard {
|
||||
font-size: 32px;
|
||||
line-height: 1em;
|
||||
}
|
||||
```
|
||||
|
||||
You can also set custom dimensions using `data-width` and `data-height` attributes:
|
||||
|
||||
```html
|
||||
<span
|
||||
data-icon="twemoji-ice-cream"
|
||||
data-width="32"
|
||||
data-height="32"
|
||||
class="iconify"
|
||||
></span>
|
||||
```
|
||||
|
||||
Sample:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-size2.png)
|
||||
|
||||
## Transformations
|
||||
|
||||
You can rotate and flip icon by adding `data-flip` and `data-rotate` attributes:
|
||||
|
||||
```html
|
||||
<span
|
||||
data-icon="twemoji-helicopter"
|
||||
class="iconify"
|
||||
data-flip="horizontal"
|
||||
></span>
|
||||
<span data-icon="twemoji-helicopter" class="iconify" data-rotate="90deg"></span>
|
||||
```
|
||||
|
||||
Possible values for `data-flip`: horizontal, vertical.
|
||||
Possible values for `data-rotate`: 90deg, 180deg, 270deg.
|
||||
|
||||
If you use both flip and rotation, the icon is flipped first, then rotated.
|
||||
|
||||
To use custom transformations use CSS transform rule. Add `!important` after rule to override the SVG inline style (inline style exists to fix a SVG rendering bug in Firefox browser).
|
||||
|
||||
```html
|
||||
<span data-icon="twemoji-helicopter" class="iconify icon-helicopter"></span>
|
||||
```
|
||||
|
||||
```css
|
||||
.icon-helicopter {
|
||||
transform: 45deg !important;
|
||||
}
|
||||
```
|
||||
|
||||
Samples:
|
||||
|
||||
![Sample](https://iconify.design/samples/icon-transform.png)
|
||||
|
||||
## Available icons
|
||||
|
||||
There are over 50,000 icons to choose from.
|
||||
|
||||
General collections (monotone icons):
|
||||
|
||||
- [Material Design Icons](https://iconify.design/icon-sets/mdi/) (5000+ icons)
|
||||
- [Unicons](https://iconify.design/icon-sets/uil/) (1000+ icons)
|
||||
- [Jam Icons](https://iconify.design/icon-sets/jam/) (900 icons)
|
||||
- [IonIcons](https://iconify.design/icon-sets/ion/) (1200+ icons)
|
||||
- [FontAwesome 4](https://iconify.design/icon-sets/fa/) and [FontAwesome 5](https://iconify.design/icon-sets/fa-solid/) (2000+ icons)
|
||||
- [Vaadin Icons](https://iconify.design/icon-sets/vaadin/) (600+ icons)
|
||||
- [Bootstrap Icons](https://iconify.design/icon-sets/bi/) (500+ icons)
|
||||
- [Feather Icons](https://iconify.design/icon-sets/feather/) and [Feather Icon](https://iconify.design/icon-sets/fe/) (500+ icons)
|
||||
- [IcoMoon Free](https://iconify.design/icon-sets/icomoon-free/) (400+ icons)
|
||||
- [Dashicons](https://iconify.design/icon-sets/dashicons/) (300 icons)
|
||||
|
||||
and many others.
|
||||
|
||||
Emoji collections (mostly colored icons):
|
||||
|
||||
- [Emoji One](https://iconify.design/icon-sets/emojione/) (1800+ colored version 2 icons, 1400+ monotone version 2 icons, 1200+ version 1 icons)
|
||||
- [OpenMoji](https://iconify.design/icon-sets/openmoji/) (3500+ icons)
|
||||
- [Noto Emoji](https://iconify.design/icon-sets/noto/) (2000+ icons for version 2, 2000+ icons for version 1)
|
||||
- [Twitter Emoji](https://iconify.design/icon-sets/twemoji/) (2000+ icons)
|
||||
- [Firefox OS Emoji](https://iconify.design/icon-sets/fxemoji/) (1000+ icons)
|
||||
|
||||
Also, there are several thematic collections, such as weather icons, map icons, etc.
|
||||
|
||||
You can use browse or search available icons on the Iconify website: https://iconify.design/icon-sets/
|
||||
|
||||
Click an icon to get HTML code.
|
||||
|
||||
## Iconify vs SVG vs glyph fonts
|
||||
|
||||
Why use Iconify instead of fonts or other frameworks?
|
||||
|
||||
There is a tutorial that explains all differences. See http://iconify.design/docs/iconify-svg-fonts/
|
||||
|
||||
## Browser support
|
||||
|
||||
Iconify supports all modern browsers.
|
||||
|
||||
Old browsers that are supported:
|
||||
|
||||
- IE 9+
|
||||
- iOS Safari for iOS 8+
|
||||
|
||||
IE 9, 10 and iOS 8 Safari do not support some modern functions that Iconify relies on. Iconify will automatically
|
||||
load polyfills for those browsers. All newer browsers do not require those polyfills.
|
||||
|
||||
## 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.
|
||||
|
||||
© 2016 - 2020 Vjacheslav Trushkin
|
43
packages/iconify/api-extractor.json
Normal file
43
packages/iconify/api-extractor.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "lib/iconify.d.ts",
|
||||
"bundledPackages": [
|
||||
"@iconify/types",
|
||||
"@iconify/core",
|
||||
"@cyberalien/redundancy"
|
||||
],
|
||||
"compiler": {},
|
||||
"apiReport": {
|
||||
"enabled": false
|
||||
},
|
||||
"docModel": {
|
||||
"enabled": false
|
||||
},
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "<projectFolder>/dist/iconify.d.ts"
|
||||
},
|
||||
"tsdocMetadata": {
|
||||
"enabled": false
|
||||
},
|
||||
"messages": {
|
||||
"compilerMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
},
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
},
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
packages/iconify/build.js
Normal file
89
packages/iconify/build.js
Normal file
@ -0,0 +1,89 @@
|
||||
const path = require('path');
|
||||
const child_process = require('child_process');
|
||||
|
||||
// List of commands to run
|
||||
const commands = [];
|
||||
|
||||
// Parse command line
|
||||
const compile = {
|
||||
core: false,
|
||||
lib: true,
|
||||
dist: true,
|
||||
api: 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',
|
||||
});
|
||||
}
|
||||
|
||||
// Compile other packages
|
||||
Object.keys(compile).forEach(key => {
|
||||
if (key !== 'core' && 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();
|
698
packages/iconify/package-lock.json
generated
Normal file
698
packages/iconify/package-lock.json
generated
Normal file
@ -0,0 +1,698 @@
|
||||
{
|
||||
"name": "@iconify/iconify",
|
||||
"version": "2.0.0-beta.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
|
||||
"integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
|
||||
"integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.0.0",
|
||||
"esutils": "^2.0.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@cyberalien/redundancy": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cyberalien/redundancy/-/redundancy-1.0.0.tgz",
|
||||
"integrity": "sha512-/tx5GpGSyMn5FrOSggDSm9yJDLcEXiK0+zdCBssST6w9QgdJjoQ9lRBSxql/3vgQoI+7XbubWsP86jjbuVnFcA==",
|
||||
"dev": true
|
||||
},
|
||||
"@microsoft/api-extractor": {
|
||||
"version": "7.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.7.12.tgz",
|
||||
"integrity": "sha512-RYMG/dIZs7VXWUgx8Cwk73Czlr9qMUMolxwStFKowy3yMluzHlAKB2srV6csoWlSms6J75tLq6z1c0LXZksWxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/api-extractor-model": "7.7.10",
|
||||
"@microsoft/tsdoc": "0.12.19",
|
||||
"@rushstack/node-core-library": "3.19.6",
|
||||
"@rushstack/ts-command-line": "4.3.13",
|
||||
"colors": "~1.2.1",
|
||||
"lodash": "~4.17.15",
|
||||
"resolve": "1.8.1",
|
||||
"source-map": "~0.6.1",
|
||||
"typescript": "~3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": {
|
||||
"version": "3.7.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
|
||||
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/api-extractor-model": {
|
||||
"version": "7.7.10",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.7.10.tgz",
|
||||
"integrity": "sha512-gMFDXwUgoQYz9TgatyNPALDdZN4xBC3Un3fGwlzME+vM13PoJ26pGuqI7kv/OlK9+q2sgrEdxWns8D3UnLf2TA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/tsdoc": "0.12.19",
|
||||
"@rushstack/node-core-library": "3.19.6"
|
||||
}
|
||||
},
|
||||
"@microsoft/tsdoc": {
|
||||
"version": "0.12.19",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.19.tgz",
|
||||
"integrity": "sha512-IpgPxHrNxZiMNUSXqR1l/gePKPkfAmIKoDRP9hp7OwjU29ZR8WCJsOJ8iBKgw0Qk+pFwR+8Y1cy8ImLY6e9m4A==",
|
||||
"dev": true
|
||||
},
|
||||
"@rollup/plugin-buble": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-buble/-/plugin-buble-0.21.1.tgz",
|
||||
"integrity": "sha512-Tmd4V95cVyGTwh7qc9ZNkg53E/isFY4q/sqZK7mSyGajYp9Wb0gbJyZWAzYlg9kZxEHmwCDlvcHDcn56SpOCCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.4",
|
||||
"@types/buble": "^0.19.2",
|
||||
"buble": "^0.19.8"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz",
|
||||
"integrity": "sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.0",
|
||||
"estree-walker": "^1.0.1",
|
||||
"is-reference": "^1.1.2",
|
||||
"magic-string": "^0.25.2",
|
||||
"resolve": "^1.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"resolve": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
|
||||
"integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz",
|
||||
"integrity": "sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.6",
|
||||
"@types/resolve": "0.0.8",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.14.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"resolve": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
|
||||
"integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-replace": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.1.tgz",
|
||||
"integrity": "sha512-qDcXj2VOa5+j0iudjb+LiwZHvBRRgWbHPhRmo1qde2KItTjuxDVQO21rp9/jOlzKR5YO0EsgRQoyox7fnL7y/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.4",
|
||||
"magic-string": "^0.25.5"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz",
|
||||
"integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@rushstack/node-core-library": {
|
||||
"version": "3.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.19.6.tgz",
|
||||
"integrity": "sha512-1+FoymIdr9W9k0D8fdZBBPwi5YcMwh/RyESuL5bY29rLICFxSLOPK+ImVZ1OcWj9GEMsvDx5pNpJ311mHQk+MA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "10.17.13",
|
||||
"colors": "~1.2.1",
|
||||
"fs-extra": "~7.0.1",
|
||||
"jju": "~1.4.0",
|
||||
"semver": "~5.3.0",
|
||||
"timsort": "~0.3.0",
|
||||
"z-schema": "~3.18.3"
|
||||
}
|
||||
},
|
||||
"@rushstack/ts-command-line": {
|
||||
"version": "4.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.3.13.tgz",
|
||||
"integrity": "sha512-BUBbjYu67NJGQkpHu8aYm7kDoMFizL1qx78+72XE3mX/vDdXYJzw/FWS7TPcMJmY4kNlYs979v2B0Q0qa2wRiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/argparse": "1.0.33",
|
||||
"argparse": "~1.0.9",
|
||||
"colors": "~1.2.1"
|
||||
}
|
||||
},
|
||||
"@types/argparse": {
|
||||
"version": "1.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.33.tgz",
|
||||
"integrity": "sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/buble": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/buble/-/buble-0.19.2.tgz",
|
||||
"integrity": "sha512-uUD8zIfXMKThmFkahTXDGI3CthFH1kMg2dOm3KLi4GlC5cbARA64bEcUMbbWdWdE73eoc/iBB9PiTMqH0dNS2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"magic-string": "^0.25.0"
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "10.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz",
|
||||
"integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
|
||||
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
|
||||
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-dynamic-import": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz",
|
||||
"integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
|
||||
"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"buble": {
|
||||
"version": "0.19.8",
|
||||
"resolved": "https://registry.npmjs.org/buble/-/buble-0.19.8.tgz",
|
||||
"integrity": "sha512-IoGZzrUTY5fKXVkgGHw3QeXFMUNBFv+9l8a4QJKG1JhG3nCMHTdEX1DCOg8568E2Q9qvAQIiSokv6Jsgx8p2cA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^6.1.1",
|
||||
"acorn-dynamic-import": "^4.0.0",
|
||||
"acorn-jsx": "^5.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"magic-string": "^0.25.3",
|
||||
"minimist": "^1.2.0",
|
||||
"os-homedir": "^2.0.0",
|
||||
"regexpu-core": "^4.5.4"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
|
||||
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz",
|
||||
"integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
|
||||
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-reference": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
|
||||
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39"
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
|
||||
"integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^6.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"supports-color": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
|
||||
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jju": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
|
||||
"integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
|
||||
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||
"dev": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-2.0.0.tgz",
|
||||
"integrity": "sha512-saRNz0DSC5C/I++gFIaJTXoFJMRwiP5zHar5vV3xQ2TkgEw6hDCcU5F272JjUylpiVgBrZNQHnfjkLabTfb92Q==",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
|
||||
"integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
|
||||
"dev": true
|
||||
},
|
||||
"regenerate-unicode-properties": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
|
||||
"integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerate": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"regexpu-core": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
|
||||
"integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerate": "^1.4.0",
|
||||
"regenerate-unicode-properties": "^8.2.0",
|
||||
"regjsgen": "^0.5.1",
|
||||
"regjsparser": "^0.6.4",
|
||||
"unicode-match-property-ecmascript": "^1.0.4",
|
||||
"unicode-match-property-value-ecmascript": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"regjsgen": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
|
||||
"integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==",
|
||||
"dev": true
|
||||
},
|
||||
"regjsparser": {
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
|
||||
"integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jsesc": "~0.5.0"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
|
||||
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "1.32.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz",
|
||||
"integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*",
|
||||
"@types/node": "*",
|
||||
"acorn": "^7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"rollup-plugin-terser": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz",
|
||||
"integrity": "sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"jest-worker": "^24.9.0",
|
||||
"rollup-pluginutils": "^2.8.2",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"terser": "^4.6.2"
|
||||
}
|
||||
},
|
||||
"rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
|
||||
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz",
|
||||
"integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
"source-map-support": "~0.5.12"
|
||||
}
|
||||
},
|
||||
"timsort": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"dev": true
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"unicode-match-property-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
|
||||
"integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"unicode-canonical-property-names-ecmascript": "^1.0.4",
|
||||
"unicode-property-aliases-ecmascript": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"unicode-match-property-value-ecmascript": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
|
||||
"integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"unicode-property-aliases-ecmascript": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
|
||||
"integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
|
||||
"dev": true
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"validator": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz",
|
||||
"integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==",
|
||||
"dev": true
|
||||
},
|
||||
"z-schema": {
|
||||
"version": "3.18.4",
|
||||
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz",
|
||||
"integrity": "sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.7.1",
|
||||
"lodash.get": "^4.0.0",
|
||||
"lodash.isequal": "^4.0.0",
|
||||
"validator": "^8.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
packages/iconify/package.json
Normal file
32
packages/iconify/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@iconify/iconify",
|
||||
"description": "Iconify common modules, used in multiple packages",
|
||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||
"version": "2.0.0-beta.0",
|
||||
"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",
|
||||
"build:api": "api-extractor run --local --verbose"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cyberalien/redundancy": "^1.0.0",
|
||||
"@iconify/core": "^1.0.0-beta.0",
|
||||
"@iconify/types": "^1.0.1",
|
||||
"@microsoft/api-extractor": "^7.7.12",
|
||||
"@rollup/plugin-buble": "^0.21.1",
|
||||
"@rollup/plugin-commonjs": "^11.0.2",
|
||||
"@rollup/plugin-node-resolve": "^7.1.1",
|
||||
"@rollup/plugin-replace": "^2.3.1",
|
||||
"rollup": "^1.32.0",
|
||||
"rollup-plugin-terser": "^5.2.0",
|
||||
"typescript": "^3.7.4"
|
||||
}
|
||||
}
|
69
packages/iconify/rollup.config.js
Normal file
69
packages/iconify/rollup.config.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import buble from '@rollup/plugin-buble';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
const name = 'iconify';
|
||||
const global = 'Iconify';
|
||||
|
||||
// Wrapper to export module as global and as ES module
|
||||
const header = `/**
|
||||
* (c) Vjacheslav Trushkin <cyberalien@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the license.txt or license.gpl.txt
|
||||
* files at https://github.com/iconify/iconify
|
||||
*
|
||||
* Licensed under Apache 2.0 or GPL 2.0 at your option.
|
||||
* If derivative product is not compatible with one of licenses, you can pick one of licenses.
|
||||
*
|
||||
* @license Apache 2.0
|
||||
* @license GPL 2.0
|
||||
*/`;
|
||||
|
||||
const footer = `
|
||||
// Export to window or web worker
|
||||
try {
|
||||
if (self.Iconify === void 0) {
|
||||
self.Iconify = Iconify;
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
// Export as ES module
|
||||
if (typeof exports === 'object') {
|
||||
try {
|
||||
exports.__esModule = true;
|
||||
exports.default = Iconify;
|
||||
} catch (err) {
|
||||
}
|
||||
}`;
|
||||
|
||||
// Get replacements
|
||||
const replacements = {};
|
||||
const packageJSON = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
replacements['__iconify_version__'] = packageJSON.version;
|
||||
|
||||
// Export configuration
|
||||
const config = [];
|
||||
[false, true].forEach(compress => {
|
||||
const item = {
|
||||
input: `lib/${name}.js`,
|
||||
output: [
|
||||
{
|
||||
file: `dist/${name}${compress ? '.min' : ''}.js`,
|
||||
format: 'iife',
|
||||
name: global,
|
||||
banner: header,
|
||||
footer,
|
||||
},
|
||||
],
|
||||
plugins: [resolve(), commonjs(), replace(replacements), buble()],
|
||||
};
|
||||
if (compress) {
|
||||
item.plugins.push(terser());
|
||||
}
|
||||
config.push(item);
|
||||
});
|
||||
export default config;
|
42
packages/iconify/src/element.ts
Normal file
42
packages/iconify/src/element.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { IconifyIconName } from '@iconify/core/lib/icon/name';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { IconifyFinder } from './interfaces/finder';
|
||||
|
||||
/**
|
||||
* Icon status
|
||||
*/
|
||||
type IconStatus = 'missing' | 'loading' | 'loaded';
|
||||
|
||||
/**
|
||||
* Data added to element to keep track of attribute changes
|
||||
*/
|
||||
export interface IconifyElementData {
|
||||
name: IconifyIconName;
|
||||
status: IconStatus;
|
||||
customisations: IconifyIconCustomisations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend Element type to allow TypeScript understand added properties
|
||||
*/
|
||||
interface IconifyElementStoredFinder {
|
||||
iconifyFinder: IconifyFinder;
|
||||
}
|
||||
|
||||
interface IconifyElementStoredData {
|
||||
iconifyData: IconifyElementData;
|
||||
}
|
||||
|
||||
export interface IconifyElement
|
||||
extends HTMLElement,
|
||||
IconifyElementStoredData,
|
||||
IconifyElementStoredFinder {}
|
||||
|
||||
/**
|
||||
* Names of properties to add to nodes
|
||||
*/
|
||||
export const elementFinderProperty: keyof IconifyElementStoredFinder = ('iconifyFinder' +
|
||||
Date.now()) as keyof IconifyElementStoredFinder;
|
||||
|
||||
export const elementDataProperty: keyof IconifyElementStoredData = ('iconifyData' +
|
||||
Date.now()) as keyof IconifyElementStoredData;
|
148
packages/iconify/src/finder.ts
Normal file
148
packages/iconify/src/finder.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import {
|
||||
elementFinderProperty,
|
||||
IconifyElement,
|
||||
elementDataProperty,
|
||||
} from './element';
|
||||
import {
|
||||
IconifyIconName,
|
||||
stringToIcon,
|
||||
validateIcon,
|
||||
} from '@iconify/core/lib/icon/name';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { IconifyFinder } from './interfaces/finder';
|
||||
|
||||
/**
|
||||
* List of modules
|
||||
*/
|
||||
const finders: IconifyFinder[] = [];
|
||||
|
||||
/**
|
||||
* Add module
|
||||
*/
|
||||
export function addFinder(finder: IconifyFinder): void {
|
||||
if (finders.indexOf(finder) === -1) {
|
||||
finders.push(finder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for found elements list
|
||||
*/
|
||||
export interface PlaceholderElement {
|
||||
element: IconifyElement;
|
||||
finder: IconifyFinder;
|
||||
name: IconifyIconName;
|
||||
customisations?: IconifyIconCustomisations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean icon name: convert from string if needed and validate
|
||||
*/
|
||||
export function cleanIconName(
|
||||
name: IconifyIconName | string | null
|
||||
): IconifyIconName | null {
|
||||
if (typeof name === 'string') {
|
||||
name = stringToIcon(name);
|
||||
}
|
||||
return name === null || !validateIcon(name) ? null : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare customisations. Returns true if identical
|
||||
*/
|
||||
function compareCustomisations(
|
||||
list1: IconifyIconCustomisations,
|
||||
list2: IconifyIconCustomisations
|
||||
): boolean {
|
||||
const keys1 = Object.keys(list1) as (keyof IconifyIconCustomisations)[];
|
||||
const keys2 = Object.keys(list2) as (keyof IconifyIconCustomisations)[];
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < keys1.length; i++) {
|
||||
const key = keys1[i];
|
||||
if (list2[key] !== list1[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all placeholders
|
||||
*/
|
||||
export function findPlaceholders(root: HTMLElement): PlaceholderElement[] {
|
||||
const results: PlaceholderElement[] = [];
|
||||
|
||||
finders.forEach(finder => {
|
||||
const elements = finder.find(root);
|
||||
Array.prototype.forEach.call(elements, item => {
|
||||
const element = item as IconifyElement;
|
||||
if (
|
||||
element[elementFinderProperty] !== void 0 &&
|
||||
element[elementFinderProperty] !== finder
|
||||
) {
|
||||
// Element is assigned to a different finder
|
||||
return;
|
||||
}
|
||||
|
||||
// Get icon name
|
||||
const name = cleanIconName(finder.name(element));
|
||||
if (name === null) {
|
||||
// Invalid name - do not assign this finder to element
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign finder to element and add it to results
|
||||
element[elementFinderProperty] = finder;
|
||||
const placeholder: PlaceholderElement = {
|
||||
element,
|
||||
finder,
|
||||
name,
|
||||
};
|
||||
results.push(placeholder);
|
||||
});
|
||||
});
|
||||
|
||||
// Find all modified SVG
|
||||
const elements = root.querySelectorAll('svg.iconify');
|
||||
Array.prototype.forEach.call(elements, item => {
|
||||
const element = item as IconifyElement;
|
||||
const finder = element[elementFinderProperty];
|
||||
const data = element[elementDataProperty];
|
||||
if (!finder || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get icon name
|
||||
const name = cleanIconName(finder.name(element));
|
||||
if (name === null) {
|
||||
// Invalid name
|
||||
return;
|
||||
}
|
||||
|
||||
let updated = false;
|
||||
let customisations;
|
||||
if (name.prefix !== data.name.prefix || name.name !== data.name.name) {
|
||||
updated = true;
|
||||
} else {
|
||||
customisations = finder.customisations(element);
|
||||
if (!compareCustomisations(data.customisations, customisations)) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add item to results
|
||||
if (updated) {
|
||||
const placeholder: PlaceholderElement = {
|
||||
element,
|
||||
finder,
|
||||
name,
|
||||
customisations,
|
||||
};
|
||||
results.push(placeholder);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
39
packages/iconify/src/finders/iconify-icon.ts
Normal file
39
packages/iconify/src/finders/iconify-icon.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { IconifyFinder } from '../interfaces/finder';
|
||||
import { IconifyElement } from '../element';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { finder as iconifyFinder } from './iconify';
|
||||
|
||||
const selector = 'iconify-icon';
|
||||
const selectors = selector + ', i.' + selector + ', span.' + selector;
|
||||
|
||||
/**
|
||||
* Export finder for:
|
||||
* <iconify-icon />
|
||||
*/
|
||||
const finder: IconifyFinder = {
|
||||
/**
|
||||
* Find all elements
|
||||
*/
|
||||
find: (root: HTMLElement): NodeList => root.querySelectorAll(selectors),
|
||||
|
||||
/**
|
||||
* Get icon name from element
|
||||
*/
|
||||
name: iconifyFinder.name,
|
||||
|
||||
/**
|
||||
* Get customisations list from element
|
||||
*/
|
||||
customisations: (node: IconifyElement): IconifyIconCustomisations => {
|
||||
return iconifyFinder.customisations(node, {
|
||||
inline: false,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter classes
|
||||
*/
|
||||
classFilter: iconifyFinder.classFilter,
|
||||
};
|
||||
|
||||
export { finder };
|
39
packages/iconify/src/finders/iconify-v1-icon.ts
Normal file
39
packages/iconify/src/finders/iconify-v1-icon.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { IconifyFinder } from '../interfaces/finder';
|
||||
import { IconifyElement } from '../element';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { finder as iconifyFinder } from './iconify-v1';
|
||||
|
||||
const selector = 'iconify-icon';
|
||||
const selectors = selector + ', i.' + selector + ', span.' + selector;
|
||||
|
||||
/**
|
||||
* Export finder for:
|
||||
* <iconify-icon />
|
||||
*/
|
||||
const finder: IconifyFinder = {
|
||||
/**
|
||||
* Find all elements
|
||||
*/
|
||||
find: (root: HTMLElement): NodeList => root.querySelectorAll(selectors),
|
||||
|
||||
/**
|
||||
* Get icon name from element
|
||||
*/
|
||||
name: iconifyFinder.name,
|
||||
|
||||
/**
|
||||
* Get customisations list from element
|
||||
*/
|
||||
customisations: (node: IconifyElement): IconifyIconCustomisations => {
|
||||
return iconifyFinder.customisations(node, {
|
||||
inline: false,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter classes
|
||||
*/
|
||||
classFilter: iconifyFinder.classFilter,
|
||||
};
|
||||
|
||||
export { finder };
|
161
packages/iconify/src/finders/iconify-v1.ts
Normal file
161
packages/iconify/src/finders/iconify-v1.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { IconifyFinder } from '../interfaces/finder';
|
||||
import { IconifyElement } from '../element';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/core/lib/customisations/rotate';
|
||||
import {
|
||||
flipFromString,
|
||||
alignmentFromString,
|
||||
} from '@iconify/core/lib/customisations/shorthand';
|
||||
|
||||
/**
|
||||
* Check if attribute exists
|
||||
*/
|
||||
function hasAttribute(element: IconifyElement, key: string) {
|
||||
return element.hasAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
function getAttribute(element: IconifyElement, key: string): string {
|
||||
return element.getAttribute(key) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
function getBooleanAttribute(
|
||||
element: IconifyElement,
|
||||
key: string
|
||||
): boolean | null {
|
||||
const value = element.getAttribute(key) as string;
|
||||
if (value === key || value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value === '' || value === 'false') {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean attributes
|
||||
*/
|
||||
const booleanAttributes: (keyof IconifyIconCustomisations)[] = [
|
||||
'inline',
|
||||
'hFlip',
|
||||
'vFlip',
|
||||
];
|
||||
|
||||
/**
|
||||
* String attributes
|
||||
*/
|
||||
const stringAttributes: (keyof IconifyIconCustomisations)[] = [
|
||||
'width',
|
||||
'height',
|
||||
];
|
||||
|
||||
/**
|
||||
* Class names
|
||||
*/
|
||||
const mainClass = 'iconify';
|
||||
|
||||
/**
|
||||
* Selector combining class names and tags
|
||||
*/
|
||||
const selector = 'i.' + mainClass + ', span.' + mainClass;
|
||||
|
||||
/**
|
||||
* Export finder for:
|
||||
* <span class="iconify" />
|
||||
* <i class="iconify" />
|
||||
* <span class="iconify-inline" />
|
||||
* <i class="iconify-inline" />
|
||||
*/
|
||||
const finder: IconifyFinder = {
|
||||
/**
|
||||
* Find all elements
|
||||
*/
|
||||
find: (root: HTMLElement): NodeList => root.querySelectorAll(selector),
|
||||
|
||||
/**
|
||||
* Get icon name from element
|
||||
*/
|
||||
name: (element: IconifyElement): string | null => {
|
||||
if (hasAttribute(element, 'data-icon')) {
|
||||
return getAttribute(element, 'data-icon');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get customisations list from element
|
||||
*/
|
||||
customisations: (
|
||||
element: IconifyElement,
|
||||
defaultValues: IconifyIconCustomisations = {
|
||||
inline: true,
|
||||
}
|
||||
): IconifyIconCustomisations => {
|
||||
const result: IconifyIconCustomisations = defaultValues;
|
||||
|
||||
// Rotation
|
||||
if (hasAttribute(element, 'data-rotate')) {
|
||||
const value = rotateFromString(
|
||||
getAttribute(element, 'data-rotate')
|
||||
);
|
||||
if (value) {
|
||||
result.rotate = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand attributes
|
||||
if (hasAttribute(element, 'data-flip')) {
|
||||
flipFromString(result, getAttribute(element, 'data-flip'));
|
||||
}
|
||||
if (hasAttribute(element, 'data-align')) {
|
||||
alignmentFromString(result, getAttribute(element, 'data-align'));
|
||||
}
|
||||
|
||||
// Boolean attributes
|
||||
booleanAttributes.forEach(attr => {
|
||||
if (hasAttribute(element, 'data-' + attr)) {
|
||||
const value = getBooleanAttribute(element, 'data-' + attr);
|
||||
if (typeof value === 'boolean') {
|
||||
(result as Record<string, boolean>)[attr] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// String attributes
|
||||
stringAttributes.forEach(attr => {
|
||||
if (hasAttribute(element, 'data-' + attr)) {
|
||||
const value = getAttribute(element, 'data-' + attr);
|
||||
if (value !== '') {
|
||||
(result as Record<string, string>)[attr] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter classes
|
||||
*/
|
||||
classFilter: (classList: string[]): string[] => {
|
||||
let result: string[] = [];
|
||||
classList.forEach(className => {
|
||||
if (
|
||||
className !== 'iconify' &&
|
||||
className !== '' &&
|
||||
className.slice(0, 9) !== 'iconify--'
|
||||
) {
|
||||
result.push(className);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
export { finder };
|
177
packages/iconify/src/finders/iconify.ts
Normal file
177
packages/iconify/src/finders/iconify.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import { IconifyFinder } from '../interfaces/finder';
|
||||
import { IconifyElement } from '../element';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/core/lib/customisations/rotate';
|
||||
import {
|
||||
flipFromString,
|
||||
alignmentFromString,
|
||||
} from '@iconify/core/lib/customisations/shorthand';
|
||||
|
||||
/**
|
||||
* Check if attribute exists
|
||||
*/
|
||||
function hasAttribute(element: IconifyElement, key: string) {
|
||||
return element.hasAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
function getAttribute(element: IconifyElement, key: string): string {
|
||||
return element.getAttribute(key) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
function getBooleanAttribute(
|
||||
element: IconifyElement,
|
||||
key: string
|
||||
): boolean | null {
|
||||
const value = element.getAttribute(key) as string;
|
||||
if (value === key || value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value === '' || value === 'false') {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean attributes
|
||||
*/
|
||||
const booleanAttributes: (keyof IconifyIconCustomisations)[] = [
|
||||
'inline',
|
||||
'hFlip',
|
||||
'vFlip',
|
||||
];
|
||||
|
||||
/**
|
||||
* String attributes
|
||||
*/
|
||||
const stringAttributes: (keyof IconifyIconCustomisations)[] = [
|
||||
'width',
|
||||
'height',
|
||||
];
|
||||
|
||||
/**
|
||||
* Class names
|
||||
*/
|
||||
const mainClass = 'iconify';
|
||||
const inlineClass = 'iconify-inline';
|
||||
|
||||
/**
|
||||
* Selector combining class names and tags
|
||||
*/
|
||||
const selector =
|
||||
'i.' +
|
||||
mainClass +
|
||||
', span.' +
|
||||
mainClass +
|
||||
', i.' +
|
||||
inlineClass +
|
||||
', span.' +
|
||||
inlineClass;
|
||||
|
||||
/**
|
||||
* Export finder for:
|
||||
* <span class="iconify" />
|
||||
* <i class="iconify" />
|
||||
* <span class="iconify-inline" />
|
||||
* <i class="iconify-inline" />
|
||||
*/
|
||||
const finder: IconifyFinder = {
|
||||
/**
|
||||
* Find all elements
|
||||
*/
|
||||
find: (root: HTMLElement): NodeList => root.querySelectorAll(selector),
|
||||
|
||||
/**
|
||||
* Get icon name from element
|
||||
*/
|
||||
name: (element: IconifyElement): string | null => {
|
||||
if (hasAttribute(element, 'data-icon')) {
|
||||
return getAttribute(element, 'data-icon');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get customisations list from element
|
||||
*/
|
||||
customisations: (
|
||||
element: IconifyElement,
|
||||
defaultValues: IconifyIconCustomisations = {
|
||||
inline: false,
|
||||
}
|
||||
): IconifyIconCustomisations => {
|
||||
const result: IconifyIconCustomisations = defaultValues;
|
||||
|
||||
// Check class list for inline class
|
||||
const className = element.getAttribute('class');
|
||||
const classList = className ? className.split(/\s+/) : [];
|
||||
if (classList.indexOf(inlineClass) !== -1) {
|
||||
result.inline = true;
|
||||
}
|
||||
|
||||
// Rotation
|
||||
if (hasAttribute(element, 'data-rotate')) {
|
||||
const value = rotateFromString(
|
||||
getAttribute(element, 'data-rotate')
|
||||
);
|
||||
if (value) {
|
||||
result.rotate = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand attributes
|
||||
if (hasAttribute(element, 'data-flip')) {
|
||||
flipFromString(result, getAttribute(element, 'data-flip'));
|
||||
}
|
||||
if (hasAttribute(element, 'data-align')) {
|
||||
alignmentFromString(result, getAttribute(element, 'data-align'));
|
||||
}
|
||||
|
||||
// Boolean attributes
|
||||
booleanAttributes.forEach(attr => {
|
||||
if (hasAttribute(element, 'data-' + attr)) {
|
||||
const value = getBooleanAttribute(element, 'data-' + attr);
|
||||
if (typeof value === 'boolean') {
|
||||
(result as Record<string, boolean>)[attr] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// String attributes
|
||||
stringAttributes.forEach(attr => {
|
||||
if (hasAttribute(element, 'data-' + attr)) {
|
||||
const value = getAttribute(element, 'data-' + attr);
|
||||
if (value !== '') {
|
||||
(result as Record<string, string>)[attr] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter classes
|
||||
*/
|
||||
classFilter: (classList: string[]): string[] => {
|
||||
let result: string[] = [];
|
||||
classList.forEach(className => {
|
||||
if (
|
||||
className !== 'iconify' &&
|
||||
className !== '' &&
|
||||
className.slice(0, 9) !== 'iconify--'
|
||||
) {
|
||||
result.push(className);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
export { finder };
|
352
packages/iconify/src/iconify.ts
Normal file
352
packages/iconify/src/iconify.ts
Normal file
@ -0,0 +1,352 @@
|
||||
// Core
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
import { merge } from '@iconify/core/lib/misc/merge';
|
||||
import {
|
||||
stringToIcon,
|
||||
validateIcon,
|
||||
IconifyIconName,
|
||||
} from '@iconify/core/lib/icon/name';
|
||||
import { IconifyIcon, FullIconifyIcon } from '@iconify/core/lib/icon';
|
||||
import {
|
||||
IconifyIconCustomisations,
|
||||
fullCustomisations,
|
||||
IconifyIconSize,
|
||||
IconifyHorizontalIconAlignment,
|
||||
IconifyVerticalIconAlignment,
|
||||
} from '@iconify/core/lib/customisations';
|
||||
import {
|
||||
getStorage,
|
||||
getIcon,
|
||||
addIcon,
|
||||
addIconSet,
|
||||
listStoredPrefixes,
|
||||
} from '@iconify/core/lib/storage';
|
||||
import { iconToSVG, IconifyIconBuildResult } from '@iconify/core/lib/builder';
|
||||
import { replaceIDs } from '@iconify/core/lib/builder/ids';
|
||||
import { calcSize } from '@iconify/core/lib/builder/calc-size';
|
||||
|
||||
// Modules
|
||||
import { coreModules } from '@iconify/core/lib/modules';
|
||||
import { browserModules } from './modules';
|
||||
|
||||
// Finders
|
||||
import { addFinder } from './finder';
|
||||
import { finder as iconifyFinder } from './finders/iconify';
|
||||
// import { finder as iconifyIconFinder } from './finders/iconify-icon';
|
||||
|
||||
// Cache
|
||||
import { storeCache, loadCache, config } from '@iconify/core/lib/cache/storage';
|
||||
|
||||
// API
|
||||
import { API } from '@iconify/core/lib/api/';
|
||||
import { setAPIModule } from '@iconify/core/lib/api/modules';
|
||||
import { setAPIConfig, IconifyAPIConfig } from '@iconify/core/lib/api/config';
|
||||
import { prepareQuery, sendQuery } from './modules/api-jsonp';
|
||||
|
||||
// Observer
|
||||
import { observer } from './modules/observer';
|
||||
|
||||
// Scan
|
||||
import { scanDOM } from './scan';
|
||||
|
||||
/**
|
||||
* Export required types
|
||||
*/
|
||||
// JSON stuff
|
||||
export { IconifyIcon, IconifyJSON };
|
||||
|
||||
// Customisations
|
||||
export {
|
||||
IconifyIconCustomisations,
|
||||
IconifyIconSize,
|
||||
IconifyHorizontalIconAlignment,
|
||||
IconifyVerticalIconAlignment,
|
||||
};
|
||||
|
||||
// Build
|
||||
export { IconifyIconBuildResult };
|
||||
|
||||
// API
|
||||
export { IconifyAPIConfig };
|
||||
|
||||
/**
|
||||
* Cache types
|
||||
*/
|
||||
export type IconifyCacheType = 'local' | 'session' | 'all';
|
||||
|
||||
/**
|
||||
* Iconify interface
|
||||
*/
|
||||
export interface IconifyGlobal {
|
||||
/* General section */
|
||||
/**
|
||||
* Get version
|
||||
*/
|
||||
getVersion: () => string;
|
||||
|
||||
/* Getting icons */
|
||||
/**
|
||||
* Check if icon exists
|
||||
*/
|
||||
iconExists: (name: string) => boolean;
|
||||
|
||||
/**
|
||||
* Get icon data with all properties
|
||||
*/
|
||||
getIcon: (name: string) => IconifyIcon | null;
|
||||
|
||||
/**
|
||||
* List all available icons
|
||||
*/
|
||||
listIcons: (prefix?: string) => string[];
|
||||
|
||||
/* Rendering icons */
|
||||
/**
|
||||
* Get icon data
|
||||
*/
|
||||
renderIcon: (
|
||||
name: string,
|
||||
customisations: IconifyIconCustomisations
|
||||
) => IconifyIconBuildResult | null;
|
||||
|
||||
/**
|
||||
* Replace IDs in icon body, should be used when parsing renderIcon() result
|
||||
*/
|
||||
replaceIDs: (body: string) => string;
|
||||
|
||||
/**
|
||||
* Calculate width knowing height and width/height ratio (or vice versa)
|
||||
*/
|
||||
calculateSize: (
|
||||
size: IconifyIconSize,
|
||||
ratio: number,
|
||||
precision?: number
|
||||
) => IconifyIconSize;
|
||||
|
||||
/* Add icons */
|
||||
/**
|
||||
* Add icon to storage
|
||||
*/
|
||||
addIcon: (name: string, data: IconifyIcon) => boolean;
|
||||
|
||||
/**
|
||||
* Add icon set to storage
|
||||
*/
|
||||
addCollection: (data: IconifyJSON) => boolean;
|
||||
|
||||
/* API stuff */
|
||||
/**
|
||||
* Pause DOM observer
|
||||
*/
|
||||
pauseObserver: () => void;
|
||||
|
||||
/**
|
||||
* Resume DOM observer
|
||||
*/
|
||||
resumeObserver: () => void;
|
||||
|
||||
/**
|
||||
* Set API configuration
|
||||
*/
|
||||
setAPIConfig: (
|
||||
customConfig: Partial<IconifyAPIConfig>,
|
||||
prefix?: string | string[]
|
||||
) => void;
|
||||
|
||||
/* Scan DOM */
|
||||
/**
|
||||
* Scan DOM
|
||||
*/
|
||||
scanDOM: (root?: HTMLElement) => void;
|
||||
|
||||
/**
|
||||
* Set root node
|
||||
*/
|
||||
setRoot: (root: HTMLElement) => void;
|
||||
|
||||
/**
|
||||
* Toggle local and session storage
|
||||
*/
|
||||
enableCache: (storage: IconifyCacheType, value: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon name
|
||||
*/
|
||||
function getIconName(name: string): IconifyIconName | null {
|
||||
const icon = stringToIcon(name);
|
||||
if (!validateIcon(icon)) {
|
||||
return null;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon data
|
||||
*/
|
||||
function getIconData(name: string): FullIconifyIcon | null {
|
||||
const icon = getIconName(name);
|
||||
return icon ? getIcon(getStorage(icon.prefix), icon.name) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG data
|
||||
*/
|
||||
function getSVG(
|
||||
name: string,
|
||||
customisations: IconifyIconCustomisations
|
||||
): IconifyIconBuildResult | null {
|
||||
// Get icon data
|
||||
const iconData = getIconData(name);
|
||||
if (!iconData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean up customisations
|
||||
const changes = fullCustomisations(customisations);
|
||||
|
||||
// Get data
|
||||
return iconToSVG(iconData, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global variable
|
||||
*/
|
||||
const Iconify: IconifyGlobal = {
|
||||
// Version
|
||||
getVersion: () => '__iconify_version__',
|
||||
|
||||
// Check if icon exists
|
||||
iconExists: (name) => getIconData(name) !== void 0,
|
||||
|
||||
// Get raw icon data
|
||||
getIcon: (name) => {
|
||||
const result = getIconData(name);
|
||||
return result ? merge(result) : null;
|
||||
},
|
||||
|
||||
// List icons
|
||||
listIcons: (prefix?: string) => {
|
||||
let icons = [];
|
||||
|
||||
let prefixes = listStoredPrefixes();
|
||||
let addPrefix = true;
|
||||
if (typeof prefix === 'string') {
|
||||
prefixes = prefixes.indexOf(prefix) !== -1 ? [] : [prefix];
|
||||
addPrefix = false;
|
||||
}
|
||||
|
||||
prefixes.forEach((prefix) => {
|
||||
const storage = getStorage(prefix);
|
||||
let icons = Object.keys(storage.icons);
|
||||
if (addPrefix) {
|
||||
icons = icons.map((name) => prefix + ':' + name);
|
||||
}
|
||||
icons = icons.concat(icons);
|
||||
});
|
||||
|
||||
return icons;
|
||||
},
|
||||
|
||||
// Render icon
|
||||
renderIcon: getSVG,
|
||||
|
||||
// Replace IDs in body
|
||||
replaceIDs: replaceIDs,
|
||||
|
||||
// Calculate size
|
||||
calculateSize: calcSize,
|
||||
|
||||
// Add icon
|
||||
addIcon: (name, data) => {
|
||||
const icon = getIconName(name);
|
||||
if (!icon) {
|
||||
return false;
|
||||
}
|
||||
const storage = getStorage(icon.prefix);
|
||||
return addIcon(storage, icon.name, data);
|
||||
},
|
||||
|
||||
// Add icon set
|
||||
addCollection: (data) => {
|
||||
if (
|
||||
typeof data !== 'object' ||
|
||||
typeof data.prefix !== 'string' ||
|
||||
!validateIcon({
|
||||
prefix: data.prefix,
|
||||
name: 'a',
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const storage = getStorage(data.prefix);
|
||||
return !!addIconSet(storage, data);
|
||||
},
|
||||
|
||||
// Pause observer
|
||||
pauseObserver: observer.pause,
|
||||
|
||||
// Resume observer
|
||||
resumeObserver: observer.resume,
|
||||
|
||||
// API configuration
|
||||
setAPIConfig: setAPIConfig,
|
||||
|
||||
// Scan DOM
|
||||
scanDOM: scanDOM,
|
||||
|
||||
// Set root node
|
||||
setRoot: (root: HTMLElement) => {
|
||||
browserModules.root = root;
|
||||
|
||||
// Restart observer
|
||||
observer.init(scanDOM);
|
||||
|
||||
// Scan DOM on next tick
|
||||
setTimeout(scanDOM);
|
||||
},
|
||||
|
||||
// Allow storage
|
||||
enableCache: (storage: IconifyCacheType, value: boolean) => {
|
||||
switch (storage) {
|
||||
case 'local':
|
||||
case 'session':
|
||||
config[storage] = value;
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
for (const key in config) {
|
||||
config[key] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise stuff
|
||||
*/
|
||||
// Add finder modules
|
||||
// addFinder(iconifyIconFinder);
|
||||
addFinder(iconifyFinder);
|
||||
|
||||
// Set cache and load existing cache
|
||||
coreModules.cache = storeCache;
|
||||
loadCache();
|
||||
|
||||
// Set API
|
||||
setAPIModule({
|
||||
send: sendQuery,
|
||||
prepare: prepareQuery,
|
||||
});
|
||||
coreModules.api = API;
|
||||
|
||||
// Load observer
|
||||
browserModules.observer = observer;
|
||||
setTimeout(() => {
|
||||
// Init on next tick when entire document has been parsed
|
||||
observer.init(scanDOM);
|
||||
});
|
||||
|
||||
export default Iconify;
|
41
packages/iconify/src/interfaces/finder.ts
Normal file
41
packages/iconify/src/interfaces/finder.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { IconifyElement } from '../element';
|
||||
import { IconifyIconName } from '@iconify/core/lib/icon/name';
|
||||
import { IconifyIconCustomisations } from '@iconify/core/lib/customisations';
|
||||
|
||||
/**
|
||||
* find - find elements that match plugin within root element
|
||||
*/
|
||||
export type IconifyFinderFind = (root: HTMLElement) => NodeList;
|
||||
|
||||
/**
|
||||
* name - get icon name from element
|
||||
*/
|
||||
export type IconifyFinderName = (
|
||||
element: IconifyElement
|
||||
) => IconifyIconName | string | null;
|
||||
|
||||
/**
|
||||
* customisations - get icon customisations
|
||||
*/
|
||||
export type IconifyFinderCustomisations = (
|
||||
element: IconifyElement,
|
||||
defaultVaues?: IconifyIconCustomisations
|
||||
) => IconifyIconCustomisations;
|
||||
|
||||
/**
|
||||
* classes - filter class list
|
||||
*/
|
||||
export type IconifyFinderClassFilter = (
|
||||
// Classes to filter
|
||||
classList: string[]
|
||||
) => string[];
|
||||
|
||||
/**
|
||||
* Interface for finder module
|
||||
*/
|
||||
export interface IconifyFinder {
|
||||
find: IconifyFinderFind;
|
||||
name: IconifyFinderName;
|
||||
customisations: IconifyFinderCustomisations;
|
||||
classFilter: IconifyFinderClassFilter;
|
||||
}
|
22
packages/iconify/src/interfaces/observer.ts
Normal file
22
packages/iconify/src/interfaces/observer.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Observer callback function
|
||||
*/
|
||||
export type ObserverCallback = (root: HTMLElement) => void;
|
||||
|
||||
/**
|
||||
* Observer functions
|
||||
*/
|
||||
type InitObserver = (callback: ObserverCallback) => void;
|
||||
type PauseObserver = () => void;
|
||||
type ResumeObserver = () => void;
|
||||
type IsObserverPaused = () => boolean;
|
||||
|
||||
/**
|
||||
* Observer functions
|
||||
*/
|
||||
export interface Observer {
|
||||
init: InitObserver;
|
||||
pause: PauseObserver;
|
||||
resume: ResumeObserver;
|
||||
isPaused: IsObserverPaused;
|
||||
}
|
25
packages/iconify/src/modules.ts
Normal file
25
packages/iconify/src/modules.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Observer } from './interfaces/observer';
|
||||
|
||||
/**
|
||||
* Dynamic modules.
|
||||
*
|
||||
* Also see modules.ts in core package.
|
||||
*/
|
||||
interface Modules {
|
||||
// Root element
|
||||
root?: HTMLElement;
|
||||
|
||||
// Observer module
|
||||
observer?: Observer;
|
||||
}
|
||||
|
||||
export const browserModules: Modules = {};
|
||||
|
||||
/**
|
||||
* Get root element
|
||||
*/
|
||||
export function getRoot(): HTMLElement {
|
||||
return browserModules.root
|
||||
? browserModules.root
|
||||
: (document.querySelector('body') as HTMLElement);
|
||||
}
|
212
packages/iconify/src/modules/api-jsonp.ts
Normal file
212
packages/iconify/src/modules/api-jsonp.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import { RedundancyPendingItem } from '@cyberalien/redundancy';
|
||||
import {
|
||||
APIQueryParams,
|
||||
IconifyAPIPrepareQuery,
|
||||
IconifyAPISendQuery,
|
||||
} from '@iconify/core/lib/api/modules';
|
||||
import { getAPIConfig } from '@iconify/core/lib/api/config';
|
||||
|
||||
/**
|
||||
* Global
|
||||
*/
|
||||
type Callback = (data: unknown) => void;
|
||||
type JSONPRoot = Record<string, Callback>;
|
||||
let global: JSONPRoot | null = null;
|
||||
|
||||
/**
|
||||
* Endpoint
|
||||
*/
|
||||
let endPoint = '{prefix}.js?icons={icons}&callback={callback}';
|
||||
|
||||
/**
|
||||
* Cache
|
||||
*/
|
||||
const maxLengthCache: Record<string, number> = Object.create(null);
|
||||
const pathCache: Record<string, string> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Get hash for query
|
||||
*
|
||||
* Hash is used in JSONP callback name, so same queries end up with same JSONP callback,
|
||||
* allowing response to be cached in browser.
|
||||
*/
|
||||
function hash(str: string): number {
|
||||
let total = 0,
|
||||
i;
|
||||
|
||||
for (i = str.length - 1; i >= 0; i--) {
|
||||
total += str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return total % 999;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root object
|
||||
*/
|
||||
function getGlobal(): JSONPRoot {
|
||||
// Create root
|
||||
if (global === null) {
|
||||
// window
|
||||
const globalRoot = (self as unknown) as Record<string, unknown>;
|
||||
|
||||
// Test for window.Iconify. If missing, create 'IconifyJSONP'
|
||||
let prefix = 'Iconify';
|
||||
let extraPrefix = '.cb';
|
||||
|
||||
if (globalRoot[prefix] === void 0) {
|
||||
// Use 'IconifyJSONP' global
|
||||
prefix = 'IconifyJSONP';
|
||||
extraPrefix = '';
|
||||
if (globalRoot[prefix] === void 0) {
|
||||
globalRoot[prefix] = Object.create(null);
|
||||
}
|
||||
global = globalRoot[prefix] as JSONPRoot;
|
||||
} else {
|
||||
// Use 'Iconify.cb'
|
||||
const iconifyRoot = globalRoot[prefix] as Record<string, JSONPRoot>;
|
||||
if (iconifyRoot.cb === void 0) {
|
||||
iconifyRoot.cb = Object.create(null);
|
||||
}
|
||||
global = iconifyRoot.cb;
|
||||
}
|
||||
|
||||
// Change end point
|
||||
endPoint = endPoint.replace(
|
||||
'{callback}',
|
||||
prefix + extraPrefix + '.{cb}'
|
||||
);
|
||||
}
|
||||
|
||||
return global;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate maximum icons list length for prefix
|
||||
*/
|
||||
function calculateMaxLength(prefix: string): number {
|
||||
// Get config and store path
|
||||
const config = getAPIConfig(prefix);
|
||||
if (!config) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate
|
||||
let result;
|
||||
if (!config.maxURL) {
|
||||
result = 0;
|
||||
} else {
|
||||
let maxHostLength = 0;
|
||||
config.resources.forEach((host) => {
|
||||
maxHostLength = Math.max(maxHostLength, host.length);
|
||||
});
|
||||
|
||||
// Make sure global is set
|
||||
getGlobal();
|
||||
|
||||
// Extra width: prefix (3) + counter (4) - '{cb}' (4)
|
||||
const extraLength = 3;
|
||||
|
||||
// Get available length
|
||||
result =
|
||||
config.maxURL -
|
||||
maxHostLength -
|
||||
config.path.length -
|
||||
endPoint.replace('{prefix}', prefix).replace('{icons}', '').length -
|
||||
extraLength;
|
||||
}
|
||||
|
||||
// Cache stuff and return result
|
||||
pathCache[prefix] = config.path;
|
||||
maxLengthCache[prefix] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare params
|
||||
*/
|
||||
export const prepareQuery: IconifyAPIPrepareQuery = (
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): APIQueryParams[] => {
|
||||
const results: APIQueryParams[] = [];
|
||||
|
||||
// Get maximum icons list length
|
||||
let maxLength = maxLengthCache[prefix];
|
||||
if (maxLength === void 0) {
|
||||
maxLength = calculateMaxLength(prefix);
|
||||
}
|
||||
|
||||
// Split icons
|
||||
let item: APIQueryParams = {
|
||||
prefix,
|
||||
icons: [],
|
||||
};
|
||||
let length = 0;
|
||||
icons.forEach((name, index) => {
|
||||
length += name.length + 1;
|
||||
if (length >= maxLength && index > 0) {
|
||||
// Next set
|
||||
results.push(item);
|
||||
item = {
|
||||
prefix,
|
||||
icons: [],
|
||||
};
|
||||
length = name.length;
|
||||
}
|
||||
|
||||
item.icons.push(name);
|
||||
});
|
||||
results.push(item);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load icons
|
||||
*/
|
||||
export const sendQuery: IconifyAPISendQuery = (
|
||||
host: string,
|
||||
params: APIQueryParams,
|
||||
status: RedundancyPendingItem
|
||||
): void => {
|
||||
const prefix = params.prefix;
|
||||
const icons = params.icons;
|
||||
const iconsList = icons.join(',');
|
||||
|
||||
// Create callback prefix
|
||||
const cbPrefix = prefix.split('-').shift().slice(0, 3);
|
||||
|
||||
const global = getGlobal();
|
||||
|
||||
// Callback hash
|
||||
let cbCounter = hash(host + ':' + prefix + ':' + iconsList);
|
||||
while (global[cbPrefix + cbCounter] !== void 0) {
|
||||
cbCounter++;
|
||||
}
|
||||
const callbackName = cbPrefix + cbCounter;
|
||||
|
||||
let path =
|
||||
pathCache[prefix] +
|
||||
endPoint
|
||||
.replace('{prefix}', prefix)
|
||||
.replace('{icons}', iconsList)
|
||||
.replace('{cb}', callbackName);
|
||||
|
||||
global[callbackName] = (data: unknown): void => {
|
||||
// Remove callback and complete query
|
||||
delete global[callbackName];
|
||||
status.done(data);
|
||||
};
|
||||
|
||||
// Create URI
|
||||
const uri = host + path;
|
||||
// console.log('API query:', uri);
|
||||
|
||||
// Create script and append it to head
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
script.src = uri;
|
||||
document.head.appendChild(script);
|
||||
};
|
177
packages/iconify/src/modules/observer.ts
Normal file
177
packages/iconify/src/modules/observer.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import { elementFinderProperty, IconifyElement } from '../element';
|
||||
import { ObserverCallback, Observer } from '../interfaces/observer';
|
||||
import { getRoot } from '../modules';
|
||||
|
||||
/**
|
||||
* MutationObserver instance, null until DOM is ready
|
||||
*/
|
||||
let instance: MutationObserver | null = null;
|
||||
|
||||
/**
|
||||
* Callback
|
||||
*/
|
||||
let callback: ObserverCallback | null = null;
|
||||
|
||||
/**
|
||||
* Parameters for mutation observer
|
||||
*/
|
||||
const observerParams: MutationObserverInit = {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Pause. Number instead of boolean to allow multiple pause/resume calls. Observer is resumed only when pause reaches 0
|
||||
*/
|
||||
let paused = 0;
|
||||
|
||||
/**
|
||||
* Scan is pending when observer is resumed
|
||||
*/
|
||||
let scanPending = false;
|
||||
|
||||
/**
|
||||
* Scan is already queued
|
||||
*/
|
||||
let scanQueued = false;
|
||||
|
||||
/**
|
||||
* Queue DOM scan
|
||||
*/
|
||||
function queueScan(): void {
|
||||
if (!scanQueued) {
|
||||
scanQueued = true;
|
||||
setTimeout(() => {
|
||||
scanQueued = false;
|
||||
scanPending = false;
|
||||
if (callback) {
|
||||
callback(getRoot());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check mutations for added nodes
|
||||
*/
|
||||
function checkMutations(mutations: MutationRecord[]): void {
|
||||
if (!scanPending) {
|
||||
for (let i = 0; i < mutations.length; i++) {
|
||||
const item = mutations[i];
|
||||
if (
|
||||
// Check for added nodes
|
||||
(item.addedNodes && item.addedNodes.length > 0) ||
|
||||
// Check for icon or placeholder with modified attributes
|
||||
(item.type === 'attributes' &&
|
||||
(item.target as IconifyElement)[elementFinderProperty] !==
|
||||
void 0)
|
||||
) {
|
||||
scanPending = true;
|
||||
if (!paused) {
|
||||
queueScan();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start/resume observer
|
||||
*/
|
||||
function observe(): void {
|
||||
if (instance) {
|
||||
instance.observe(getRoot(), observerParams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start mutation observer
|
||||
*/
|
||||
function startObserver(): void {
|
||||
if (instance !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scanPending = true;
|
||||
instance = new MutationObserver(checkMutations);
|
||||
observe();
|
||||
if (!paused) {
|
||||
queueScan();
|
||||
}
|
||||
}
|
||||
|
||||
// Fake interface to test old IE properties
|
||||
interface OldIEElement extends HTMLElement {
|
||||
doScroll?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export module
|
||||
*/
|
||||
export const observer: Observer = {
|
||||
/**
|
||||
* Start observer when DOM is ready
|
||||
*/
|
||||
init: (cb: ObserverCallback): void => {
|
||||
callback = cb;
|
||||
|
||||
if (instance && !paused) {
|
||||
// Restart observer
|
||||
instance.disconnect();
|
||||
observe();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const doc = document;
|
||||
if (
|
||||
doc.readyState === 'complete' ||
|
||||
(doc.readyState !== 'loading' &&
|
||||
!(doc.documentElement as OldIEElement).doScroll)
|
||||
) {
|
||||
startObserver();
|
||||
} else {
|
||||
doc.addEventListener('DOMContentLoaded', startObserver);
|
||||
window.addEventListener('load', startObserver);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause observer
|
||||
*/
|
||||
pause: (): void => {
|
||||
paused++;
|
||||
if (paused > 1 || instance === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check pending records, stop observer
|
||||
checkMutations(instance.takeRecords());
|
||||
instance.disconnect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume observer
|
||||
*/
|
||||
resume: (): void => {
|
||||
if (!paused) {
|
||||
return;
|
||||
}
|
||||
paused--;
|
||||
|
||||
if (!paused && instance) {
|
||||
observe();
|
||||
if (scanPending) {
|
||||
queueScan();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if observer is paused
|
||||
*/
|
||||
isPaused: (): boolean => paused > 0,
|
||||
};
|
109
packages/iconify/src/render.ts
Normal file
109
packages/iconify/src/render.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { PlaceholderElement } from './finder';
|
||||
import { FullIconifyIcon } from '@iconify/core/lib/icon';
|
||||
import {
|
||||
IconifyIconCustomisations,
|
||||
fullCustomisations,
|
||||
} from '@iconify/core/lib/customisations';
|
||||
import { iconToSVG } from '@iconify/core/lib/builder';
|
||||
import { replaceIDs } from '@iconify/core/lib/builder/ids';
|
||||
import {
|
||||
IconifyElement,
|
||||
IconifyElementData,
|
||||
elementDataProperty,
|
||||
elementFinderProperty,
|
||||
} from './element';
|
||||
|
||||
/**
|
||||
* Replace element with SVG
|
||||
*/
|
||||
export function renderIcon(
|
||||
placeholder: PlaceholderElement,
|
||||
customisations: IconifyIconCustomisations,
|
||||
iconData: FullIconifyIcon
|
||||
): IconifyElement | null {
|
||||
const data = iconToSVG(iconData, fullCustomisations(customisations));
|
||||
|
||||
// Get class name
|
||||
const placeholderElement = placeholder.element;
|
||||
const placeholderClassName = placeholderElement.getAttribute('class');
|
||||
const filteredClassList = placeholder.finder.classFilter(
|
||||
placeholderClassName ? placeholderClassName.split(/\s+/) : []
|
||||
);
|
||||
const className =
|
||||
'iconify iconify--' +
|
||||
placeholder.name.prefix +
|
||||
(filteredClassList.length ? ' ' + filteredClassList.join(' ') : '');
|
||||
|
||||
// Generate SVG as string
|
||||
const html =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" class="' +
|
||||
className +
|
||||
'">' +
|
||||
replaceIDs(data.body) +
|
||||
'</svg>';
|
||||
|
||||
// Create placeholder. Why placeholder? IE11 doesn't support innerHTML method on SVG.
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = html;
|
||||
|
||||
// Get SVG element
|
||||
const svg = span.childNodes[0] as IconifyElement;
|
||||
const svgStyle = svg.style;
|
||||
|
||||
// Add attributes
|
||||
const svgAttributes = data.attributes as Record<string, string>;
|
||||
Object.keys(svgAttributes).forEach((attr) => {
|
||||
svg.setAttribute(attr, svgAttributes[attr]);
|
||||
});
|
||||
|
||||
// Add custom styles
|
||||
svgStyle.transform = 'rotate(360deg)';
|
||||
if (data.inline) {
|
||||
svgStyle.verticalAlign = '-0.125em';
|
||||
}
|
||||
|
||||
// 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];
|
||||
svgStyle[attr] = placeholderStyle[attr];
|
||||
}
|
||||
|
||||
// Store data
|
||||
const elementData: IconifyElementData = {
|
||||
name: placeholder.name,
|
||||
status: 'loaded',
|
||||
customisations: customisations,
|
||||
};
|
||||
svg[elementDataProperty] = elementData;
|
||||
svg[elementFinderProperty] = placeholder.finder;
|
||||
|
||||
// Replace placeholder
|
||||
if (placeholderElement.parentNode) {
|
||||
placeholderElement.parentNode.replaceChild(svg, placeholderElement);
|
||||
} else {
|
||||
// Placeholder has no parent? Remove SVG parent as well
|
||||
span.removeChild(svg);
|
||||
}
|
||||
|
||||
// Return new node
|
||||
return svg;
|
||||
}
|
165
packages/iconify/src/scan.ts
Normal file
165
packages/iconify/src/scan.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { IconifyIconName } from '@iconify/core/lib/icon/name';
|
||||
import { getStorage, getIcon } from '@iconify/core/lib/storage';
|
||||
import { coreModules } from '@iconify/core/lib/modules';
|
||||
import { FullIconifyIcon } from '@iconify/core/lib/icon';
|
||||
import { findPlaceholders } from './finder';
|
||||
import { browserModules, getRoot } from './modules';
|
||||
import { IconifyElementData, elementDataProperty } from './element';
|
||||
import { renderIcon } from './render';
|
||||
|
||||
/**
|
||||
* Flag to avoid scanning DOM too often
|
||||
*/
|
||||
let scanQueued = false;
|
||||
|
||||
/**
|
||||
* Icons have been loaded
|
||||
*/
|
||||
function checkPendingIcons(): void {
|
||||
if (!scanQueued) {
|
||||
scanQueued = true;
|
||||
setTimeout(() => {
|
||||
if (scanQueued) {
|
||||
scanQueued = false;
|
||||
scanDOM();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare Icon objects. Returns true if icons are identical.
|
||||
*
|
||||
* Note: null means icon is invalid, so null to null comparison = false.
|
||||
*/
|
||||
const compareIcons = (
|
||||
icon1: IconifyIconName | null,
|
||||
icon2: IconifyIconName | null
|
||||
): boolean => {
|
||||
return (
|
||||
icon1 !== null &&
|
||||
icon2 !== null &&
|
||||
icon1.name === icon2.name &&
|
||||
icon1.prefix === icon2.prefix
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan DOM for placeholders
|
||||
*/
|
||||
export function scanDOM(root?: HTMLElement): void {
|
||||
scanQueued = false;
|
||||
|
||||
// Observer
|
||||
let paused = false;
|
||||
|
||||
// List of icons to load
|
||||
const loadIcons: Record<string, Record<string, boolean>> = Object.create(
|
||||
null
|
||||
);
|
||||
|
||||
// Get root node and placeholders
|
||||
if (!root) {
|
||||
root = getRoot();
|
||||
}
|
||||
findPlaceholders(root).forEach(item => {
|
||||
const element = item.element;
|
||||
const iconName = item.name;
|
||||
const prefix = iconName.prefix;
|
||||
const name = iconName.name;
|
||||
let data: IconifyElementData = element[elementDataProperty];
|
||||
|
||||
// Icon has not been updated since last scan
|
||||
if (data !== void 0 && compareIcons(data.name, iconName)) {
|
||||
// Icon name was not changed and data is set - quickly return if icon is missing or still loading
|
||||
switch (data.status) {
|
||||
case 'missing':
|
||||
return;
|
||||
|
||||
case 'loading':
|
||||
if (
|
||||
coreModules.api &&
|
||||
coreModules.api.isPending(prefix, name)
|
||||
) {
|
||||
// Pending
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check icon
|
||||
const storage = getStorage(prefix);
|
||||
if (storage.icons[name] !== void 0) {
|
||||
// Icon exists - replace placeholder
|
||||
if (browserModules.observer && !paused) {
|
||||
browserModules.observer.pause();
|
||||
paused = true;
|
||||
}
|
||||
|
||||
// Get customisations
|
||||
const customisations =
|
||||
item.customisations !== void 0
|
||||
? item.customisations
|
||||
: item.finder.customisations(element);
|
||||
|
||||
// Render icon
|
||||
renderIcon(
|
||||
item,
|
||||
customisations,
|
||||
getIcon(storage, name) as FullIconifyIcon
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (storage.missing[name]) {
|
||||
// Mark as missing
|
||||
data = {
|
||||
name: iconName,
|
||||
status: 'missing',
|
||||
customisations: {},
|
||||
};
|
||||
element[elementDataProperty] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (coreModules.api) {
|
||||
if (!coreModules.api.isPending(prefix, name)) {
|
||||
// Add icon to loading queue
|
||||
if (loadIcons[prefix] === void 0) {
|
||||
loadIcons[prefix] = Object.create(null);
|
||||
}
|
||||
loadIcons[prefix][name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as loading
|
||||
data = {
|
||||
name: iconName,
|
||||
status: 'loading',
|
||||
customisations: {},
|
||||
};
|
||||
element[elementDataProperty] = data;
|
||||
});
|
||||
|
||||
// Load icons
|
||||
if (coreModules.api) {
|
||||
const api = coreModules.api;
|
||||
Object.keys(loadIcons).forEach(prefix => {
|
||||
api.loadIcons(
|
||||
Object.keys(loadIcons[prefix]).map(name => {
|
||||
const icon: IconifyIconName = {
|
||||
prefix,
|
||||
name,
|
||||
};
|
||||
return icon;
|
||||
}),
|
||||
checkPendingIcons
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (browserModules.observer && paused) {
|
||||
browserModules.observer.resume();
|
||||
}
|
||||
}
|
14
packages/iconify/tsconfig.json
Normal file
14
packages/iconify/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"target": "ES2019",
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"sourceMap": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
23
packages/react-demo/.gitignore
vendored
Normal file
23
packages/react-demo/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
68
packages/react-demo/README.md
Normal file
68
packages/react-demo/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
|
14882
packages/react-demo/package-lock.json
generated
Normal file
14882
packages/react-demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user