mirror of
https://github.com/iconify/iconify.git
synced 2024-12-05 02:33:16 +00:00
Rewrite SVG framework with new unit tests, better attribute watching, cleaner render
This commit is contained in:
parent
70ed79a516
commit
7700ea1808
@ -64,7 +64,6 @@ Other packages:
|
||||
- [Sapper demo](./demo/sapper-demo/) - demo for Sapper, using Svelte component on the server and in the browser. Run `npm run dev` to start the demo (deprecated, use SvelteKit instead of Sapper).
|
||||
- [SvelteKit demo](./demo/sveltekit-demo/) - demo for SvelteKit, using Svelte component on the server and in the browser. Run `npm run dev` to start the demo.
|
||||
- [Ember demo](./demo/ember-demo/) - demo for Ember component. Run `npm run start` to start demo.
|
||||
- [Browser tests](./demo/browser-tests/) - unit tests for SVG framework. Run `npm run build` to build it. Open test.html in browser (requires HTTP server).
|
||||
|
||||
## Installation
|
||||
|
||||
@ -107,7 +106,7 @@ This monorepo uses symbolic links to create links between packages. This allows
|
||||
When using Windows, symbolic links require setting up extra permissions. If you are using Windows and cannot set permissions for symbolic links, there are several options:
|
||||
|
||||
- Use Windows Subsystem for Linux (WSL).
|
||||
- Treat each package as a separate package, without links to other packages. All packages do have correct dependencies, so you will be able to use most packages (except for `browser-tests` that requires links to access directory `lib` from `iconify` package), but you will not be able to work on multiple packages at the same time.
|
||||
- Treat each package as a separate package, without links to other packages. All packages do have correct dependencies, so you will be able to use most packages, but you will not be able to work on multiple packages at the same time.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
4
demo/browser-tests/.gitignore
vendored
4
demo/browser-tests/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
lib
|
||||
dist
|
@ -1,145 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const { dirname } = require('path');
|
||||
const child_process = require('child_process');
|
||||
|
||||
const testsDir = __dirname;
|
||||
const librariesDir = dirname(dirname(testsDir)) + '/packages';
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if required modules in same monorepo are available
|
||||
const fileExists = (file) => {
|
||||
try {
|
||||
fs.statSync(file);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (compile.dist && !fileExists(testsDir + '/lib/node.js')) {
|
||||
compile.lib = true;
|
||||
}
|
||||
|
||||
if (
|
||||
compile.lib &&
|
||||
(!fileExists(librariesDir + '/iconify/dist/iconify.js') ||
|
||||
!fileExists(librariesDir + '/iconify/lib/iconify.js'))
|
||||
) {
|
||||
compile.iconify = true;
|
||||
}
|
||||
|
||||
if (compile.iconify && !fileExists(librariesDir + '/core/lib/modules.mjs')) {
|
||||
compile.core = true;
|
||||
}
|
||||
|
||||
// Compile core before compiling this package
|
||||
if (compile.core) {
|
||||
commands.push({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build'],
|
||||
cwd: librariesDir + '/core',
|
||||
});
|
||||
}
|
||||
|
||||
if (compile.iconify || compile.core) {
|
||||
commands.push({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build'],
|
||||
cwd: librariesDir + '/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();
|
||||
|
||||
// Update version number in package.json
|
||||
const packageJSON = JSON.parse(
|
||||
fs.readFileSync(testsDir + '/package.json', 'utf8')
|
||||
);
|
||||
let iconifyVersion = packageJSON.devDependencies['@iconify/iconify'].replace(
|
||||
/[\^~]/g,
|
||||
''
|
||||
);
|
||||
if (packageJSON.version !== iconifyVersion) {
|
||||
console.log('Updated package version to', iconifyVersion);
|
||||
packageJSON.version = iconifyVersion;
|
||||
fs.writeFileSync(
|
||||
testsDir + '/package.json',
|
||||
JSON.stringify(packageJSON, null, '\t') + '\n',
|
||||
'utf8'
|
||||
);
|
||||
}
|
2394
demo/browser-tests/package-lock.json
generated
2394
demo/browser-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "@iconify-demo/browser-tests",
|
||||
"private": true,
|
||||
"description": "Browser tests for @iconify/iconify package",
|
||||
"author": "Vjacheslav Trushkin <cyberalien@gmail.com> (https://iconify.design)",
|
||||
"version": "2.2.1",
|
||||
"license": "(Apache-2.0 OR GPL-2.0)",
|
||||
"bugs": "https://github.com/iconify/iconify/issues",
|
||||
"homepage": "https://iconify.design/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconify/iconify.git",
|
||||
"directory": "packages/browser-tests"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build",
|
||||
"build:lib": "tsc -b",
|
||||
"build:dist": "rollup -c rollup.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/iconify": "^2.2.1",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.2.0",
|
||||
"rollup": "^2.66.0",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
|
||||
const match = '-test.ts';
|
||||
|
||||
// Find files
|
||||
let files = fs
|
||||
.readdirSync('tests')
|
||||
.sort()
|
||||
.filter((file) => file.slice(0 - match.length) === match);
|
||||
|
||||
// Remove suffix
|
||||
files = files.map((file) => file.slice(0, file.length - match.length));
|
||||
|
||||
// Debug one test
|
||||
// files = ['21-scan-dom-api'];
|
||||
|
||||
// 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,
|
||||
// exxtensions: ['.js'],
|
||||
}),
|
||||
commonjs({
|
||||
ignore: ['cross-fetch'],
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// 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;
|
@ -1,201 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
|
||||
import { setAPIModule } from '@iconify/core/lib/api/modules';
|
||||
import { addAPIProvider } from '@iconify/core/lib/api/config';
|
||||
import { loadIcons } from '@iconify/core/lib/api/icons';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
return 'fake-api-' + prefixCounter++;
|
||||
}
|
||||
|
||||
describe('Testing fake API', () => {
|
||||
before(() => {
|
||||
setAPIModule('', {
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
});
|
||||
});
|
||||
|
||||
it('Loading results', (done) => {
|
||||
const provider = nextPrefix();
|
||||
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,
|
||||
},
|
||||
};
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
setFakeData(provider, prefix, data);
|
||||
|
||||
// Attempt to load icons
|
||||
loadIcons(
|
||||
[
|
||||
provider + ':' + prefix + ':icon1',
|
||||
provider + ':' + prefix + ':icon2',
|
||||
],
|
||||
(loaded, missing, pending) => {
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Loading results with delay', (done) => {
|
||||
const provider = nextPrefix();
|
||||
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,
|
||||
},
|
||||
};
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
setFakeData(provider, prefix, data);
|
||||
|
||||
// Attempt to load icons
|
||||
const start = Date.now();
|
||||
loadIcons(
|
||||
[
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
],
|
||||
(loaded, missing, pending) => {
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
{
|
||||
provider,
|
||||
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 provider = nextPrefix();
|
||||
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,
|
||||
},
|
||||
};
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
rotate: 20,
|
||||
timeout: 100,
|
||||
});
|
||||
setFakeData(provider, prefix, data);
|
||||
|
||||
// Attempt to load icons
|
||||
let counter = 0;
|
||||
loadIcons(
|
||||
[
|
||||
provider + ':' + prefix + ':icon1',
|
||||
provider + ':' + prefix + ':icon2',
|
||||
],
|
||||
(loaded, missing, pending) => {
|
||||
try {
|
||||
counter++;
|
||||
switch (counter) {
|
||||
case 1:
|
||||
// Loaded icon1
|
||||
expect(loaded).to.be.eql([
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon1',
|
||||
},
|
||||
]);
|
||||
expect(pending).to.be.eql([
|
||||
{
|
||||
provider,
|
||||
prefix,
|
||||
name: 'icon2',
|
||||
},
|
||||
]);
|
||||
expect(missing).to.be.eql([]);
|
||||
done();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
done(
|
||||
'Callback should not be called ' +
|
||||
counter +
|
||||
' times.'
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
@ -1,347 +0,0 @@
|
||||
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/modules/element';
|
||||
import { IconifyIconCustomisations } from '@iconify/utils/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>Testing <span>icons</span> placeholders (not replaced with SVG)</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 =
|
||||
'This test does not render SVG!<br />' +
|
||||
'Block icons:' +
|
||||
' <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 =
|
||||
'This test does not render SVG!<br />' +
|
||||
'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 =
|
||||
'This test does not render SVG!<br />' +
|
||||
'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);
|
||||
});
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import {
|
||||
addFinder,
|
||||
findPlaceholders,
|
||||
} from '@iconify/iconify/lib/modules/finder';
|
||||
import { IconifyFinder } from '@iconify/iconify/lib/finders/interface';
|
||||
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/utils/lib/icon/name';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Testing legacy finder', () => {
|
||||
before(() => {
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
});
|
||||
|
||||
it('Finding nodes', () => {
|
||||
const node = getNode('finder');
|
||||
node.innerHTML =
|
||||
'<div><p>List of <span>icon</span> placeholders (this test does not render SVG)</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(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
},
|
||||
iconifyFinder
|
||||
);
|
||||
|
||||
testIcon(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
},
|
||||
iconifyFinder
|
||||
);
|
||||
|
||||
testIcon(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'ic',
|
||||
name: 'baseline-account',
|
||||
},
|
||||
iconifyIconFinder
|
||||
);
|
||||
|
||||
// End of list
|
||||
expect(items.shift()).to.be.equal(void 0);
|
||||
});
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import {
|
||||
addFinder,
|
||||
findPlaceholders,
|
||||
} from '@iconify/iconify/lib/modules/finder';
|
||||
import { IconifyFinder } from '@iconify/iconify/lib/finders/interface';
|
||||
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/utils/lib/icon/name';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Testing finder', () => {
|
||||
before(() => {
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
});
|
||||
|
||||
it('Finding nodes', () => {
|
||||
const node = getNode('finder');
|
||||
node.innerHTML =
|
||||
'<div><p>List of <span>icon</span> placeholders (this test does not render SVG)</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(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
},
|
||||
iconifyFinder
|
||||
);
|
||||
|
||||
testIcon(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'account',
|
||||
},
|
||||
iconifyFinder
|
||||
);
|
||||
|
||||
testIcon(
|
||||
{
|
||||
provider: '',
|
||||
prefix: 'ic',
|
||||
name: 'baseline-account',
|
||||
},
|
||||
iconifyIconFinder
|
||||
);
|
||||
|
||||
// End of list
|
||||
expect(items.shift()).to.be.equal(void 0);
|
||||
});
|
||||
});
|
@ -1,219 +0,0 @@
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,55 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode, setRoot } from './node';
|
||||
import { listRootNodes } from '@iconify/iconify/lib/modules/root';
|
||||
import {
|
||||
initObserver,
|
||||
pauseObserver,
|
||||
} 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');
|
||||
setRoot(node);
|
||||
|
||||
// Get node
|
||||
const list = listRootNodes();
|
||||
expect(list.length).to.be.equal(1);
|
||||
|
||||
const item = list[0];
|
||||
expect(item.node).to.be.equal(node);
|
||||
expect(item.observer).to.be.equal(void 0);
|
||||
|
||||
// Do test
|
||||
let counter = 0;
|
||||
|
||||
node.innerHTML = '<div></div><ul><li>test</li><li>test2</li></ul>';
|
||||
initObserver((root) => {
|
||||
expect(root.node).to.be.equal(node);
|
||||
|
||||
counter++;
|
||||
|
||||
// Should be called only once
|
||||
expect(counter).to.be.equal(1);
|
||||
|
||||
// Check if observer is paused
|
||||
expect(item.observer).to.not.be.equal(void 0);
|
||||
expect(item.observer.paused).to.be.equal(0);
|
||||
|
||||
// Pause observer
|
||||
pauseObserver();
|
||||
expect(item.observer.paused).to.be.equal(1);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
// Add few nodes to trigger observer
|
||||
expect(item.observer).to.not.be.equal(void 0);
|
||||
expect(item.observer.paused).to.be.equal(0);
|
||||
node.querySelector('div').innerHTML =
|
||||
'<span class="test">Some text</span><i>!</i>';
|
||||
});
|
||||
});
|
@ -1,112 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode, setRoot } from './node';
|
||||
import { elementFinderProperty } from '@iconify/iconify/lib/modules/element';
|
||||
import {
|
||||
initObserver,
|
||||
pauseObserver,
|
||||
resumeObserver,
|
||||
} 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');
|
||||
setRoot(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>';
|
||||
initObserver((root) => {
|
||||
expect(root.node).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');
|
||||
pauseObserver();
|
||||
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';
|
||||
resumeObserver();
|
||||
|
||||
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
|
||||
node.querySelector('div').innerHTML =
|
||||
'<span class="test">Some text</span><i>!</i>';
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,124 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode, setRoot } from './node';
|
||||
import { addFinder } from '@iconify/iconify/lib/modules/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/storage';
|
||||
import { listRootNodes } from '@iconify/iconify/lib/modules/root';
|
||||
import { scanDOM, scanElement } from '@iconify/iconify/lib/modules/scanner';
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
// Sanity check before running tests
|
||||
expect(listRootNodes()).to.be.eql([]);
|
||||
|
||||
it('Scan DOM with preloaded icons', () => {
|
||||
const node = getNode('scan-dom');
|
||||
setRoot(node);
|
||||
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM (should render SVG!)</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>';
|
||||
|
||||
// Scan node
|
||||
scanDOM();
|
||||
|
||||
// Find elements
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(4);
|
||||
|
||||
// Check root nodes list
|
||||
const nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(node);
|
||||
});
|
||||
|
||||
it('Scan DOM with unattached root', () => {
|
||||
const fakeNode = getNode('scan-dom');
|
||||
setRoot(fakeNode);
|
||||
|
||||
const node = document.createElement('div');
|
||||
|
||||
node.innerHTML = '<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Check root nodes list
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeNode);
|
||||
|
||||
// Scan node
|
||||
scanElement(node);
|
||||
|
||||
// Find elements
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
// Make sure tempoary node was not added as root
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeNode);
|
||||
});
|
||||
|
||||
it('Scan DOM with icon as root', () => {
|
||||
const fakeNode = getNode('scan-dom');
|
||||
setRoot(fakeNode);
|
||||
|
||||
const node = document.createElement('span');
|
||||
node.setAttribute('data-icon', 'mdi:home');
|
||||
|
||||
// Check root nodes list
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeNode);
|
||||
|
||||
// Scan node
|
||||
scanElement(node);
|
||||
|
||||
// Check node
|
||||
expect(node.tagName).to.be.equal('SPAN');
|
||||
expect(node.innerHTML).to.be.equal('');
|
||||
|
||||
// Make sure tempoary node was not added as root
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeNode);
|
||||
});
|
||||
});
|
@ -1,280 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode, setRoot } from './node';
|
||||
import { addFinder } from '@iconify/iconify/lib/modules/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/storage';
|
||||
import { listRootNodes } from '@iconify/iconify/lib/modules/root';
|
||||
import { scanDOM } from '@iconify/iconify/lib/modules/scanner';
|
||||
import {
|
||||
initObserver,
|
||||
observe,
|
||||
stopObserving,
|
||||
} from '@iconify/iconify/lib/modules/observer';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Observe DOM', () => {
|
||||
const storage = getStorage('', 'mdi');
|
||||
|
||||
before(() => {
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
|
||||
// Add mentioned icons to storage
|
||||
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('Basic test', (done) => {
|
||||
const node = getNode('observe-dom');
|
||||
const ignoredNode = getNode('observe-dom');
|
||||
|
||||
// Set root and init observer
|
||||
setRoot(node);
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Test listRootNodes
|
||||
const nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(node);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Set HTML
|
||||
node.innerHTML =
|
||||
'<p>Testing observing DOM (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
ignoredNode.innerHTML =
|
||||
'<p>This node should be ignored</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Test nodes
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
let elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
elements = ignoredNode.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(
|
||||
0,
|
||||
'Looks like document.body is observed!'
|
||||
);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('Change icon', (done) => {
|
||||
const node = getNode('observe-dom');
|
||||
|
||||
// Set root and init observer
|
||||
setRoot(node);
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Set HTML
|
||||
node.innerHTML =
|
||||
'<p>Testing observing DOM (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Test nodes
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
|
||||
|
||||
// Change icon
|
||||
elements[0].setAttribute('data-icon', 'mdi:account');
|
||||
|
||||
// Test nodes after timer
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.be.equal(-1);
|
||||
expect(node.innerHTML.indexOf('M12 4a4')).to.not.be.equal(-1);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('Adding node to observe', (done) => {
|
||||
const baseNode = getNode('observe-dom');
|
||||
const node = getNode('observe-dom');
|
||||
|
||||
// Set root and init observer
|
||||
setRoot(baseNode);
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Test listRootNodes
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(baseNode);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Observe another node
|
||||
observe(node);
|
||||
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(2);
|
||||
expect(nodes[0].node).to.be.equal(baseNode);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
expect(nodes[1].node).to.be.equal(node);
|
||||
expect(nodes[1].temporary).to.be.equal(false);
|
||||
|
||||
// Set HTML
|
||||
baseNode.innerHTML =
|
||||
'<p>Testing observing 2 nodes (1) (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
node.innerHTML =
|
||||
'<p>Testing observing 2 nodes (2) (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Test nodes
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
let elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
elements = baseNode.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
|
||||
expect(baseNode.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(
|
||||
-1
|
||||
);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('Adding node to observe after setting content', (done) => {
|
||||
const baseNode = getNode('observe-dom');
|
||||
const node = getNode('observe-dom');
|
||||
|
||||
// Set root and init observer
|
||||
setRoot(baseNode);
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Test listRootNodes
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(baseNode);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Set HTML
|
||||
baseNode.innerHTML =
|
||||
'<p>Testing observing 2 nodes (1) (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
node.innerHTML =
|
||||
'<p>Testing observing 2 nodes (2) (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Observe node: should run scan on next tick
|
||||
observe(node);
|
||||
|
||||
// Test nodes
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
let elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
elements = baseNode.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
|
||||
expect(baseNode.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(
|
||||
-1
|
||||
);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('Stop observing node', (done) => {
|
||||
const baseNode = getNode('observe-dom');
|
||||
const node = getNode('observe-dom');
|
||||
|
||||
// Set root and init observer
|
||||
setRoot(baseNode);
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Test listRootNodes
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(baseNode);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Observe another node
|
||||
observe(node);
|
||||
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(2);
|
||||
expect(nodes[0].node).to.be.equal(baseNode);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
expect(nodes[1].node).to.be.equal(node);
|
||||
expect(nodes[1].temporary).to.be.equal(false);
|
||||
|
||||
// Stop observing baseNode
|
||||
stopObserving(baseNode);
|
||||
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(node);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Set HTML
|
||||
baseNode.innerHTML =
|
||||
'<p>Testing observing 2 nodes (1) (should NOT render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
node.innerHTML =
|
||||
'<p>Testing observing 2 nodes (2) (should render SVG!)</p>' +
|
||||
'<span class="iconify" data-icon="mdi:home"></span>';
|
||||
|
||||
// Test nodes
|
||||
setTimeout(() => {
|
||||
// Find elements
|
||||
let elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(1);
|
||||
|
||||
elements = baseNode.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(0);
|
||||
|
||||
// Test for "home" icon contents
|
||||
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
|
||||
expect(baseNode.innerHTML.indexOf('20v-6h4v6h5v')).to.be.equal(-1);
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
@ -1,465 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode, setRoot } from './node';
|
||||
import { addFinder } from '@iconify/iconify/lib/modules/finder';
|
||||
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
|
||||
import { setAPIModule } from '@iconify/core/lib/api/modules';
|
||||
import { addAPIProvider } from '@iconify/core/lib/api/config';
|
||||
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
|
||||
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
|
||||
import { listRootNodes } from '@iconify/iconify/lib/modules/root';
|
||||
import { scanDOM, scanElement } from '@iconify/iconify/lib/modules/scanner';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
let prefixCounter = 0;
|
||||
function nextPrefix(): string {
|
||||
return 'scan-dom-api-' + prefixCounter++;
|
||||
}
|
||||
|
||||
describe('Scanning DOM with API', () => {
|
||||
before(() => {
|
||||
// Add finders
|
||||
addFinder(iconifyFinder);
|
||||
addFinder(iconifyIconFinder);
|
||||
|
||||
// Set API
|
||||
setAPIModule('', {
|
||||
prepare: prepareQuery,
|
||||
send: sendQuery,
|
||||
});
|
||||
});
|
||||
|
||||
it('Scan DOM with API', (done) => {
|
||||
const provider = nextPrefix();
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
|
||||
// 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(provider, 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(provider, prefix2, data2);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API (should render SVG!)</p><ul>' +
|
||||
'<li>Inline icons:' +
|
||||
' <span class="iconify iconify-inline" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>Block icons:' +
|
||||
' <iconify-icon data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <i class="iconify-icon" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></i>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
// Scan DOM
|
||||
setRoot(node);
|
||||
scanDOM();
|
||||
|
||||
// Test listRootNodes
|
||||
const nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(node);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// 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 provider = nextPrefix();
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
|
||||
// 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(provider, 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(provider, 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(provider, prefix1, data1b);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API: renamed icon (should render SVG!)</p><ul>' +
|
||||
'<li>Default finder:' +
|
||||
' <span class="iconify-inline first-icon" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify-inline second-icon iconify--mdi-account" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>IconifyIcon finder:' +
|
||||
' <iconify-icon class="third-icon" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <iconify-icon class="fourth-icon" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
// Scan DOM
|
||||
setRoot(node);
|
||||
scanDOM();
|
||||
|
||||
// Test listRootNodes
|
||||
const nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(node);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// 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',
|
||||
'@' + provider + ':' + 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 provider = nextPrefix();
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
|
||||
// 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(provider, 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(provider, prefix2, data2);
|
||||
|
||||
const node = getNode('scan-dom');
|
||||
node.innerHTML =
|
||||
'<div><p>Testing scanning DOM with API: invalid name (should render 3 SVGs!)</p><ul>' +
|
||||
'<li>Inline icons (2 valid):' +
|
||||
' <span class="iconify" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
|
||||
' <i class="iconify test-icon iconify--mdi-account" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
|
||||
'</li>' +
|
||||
'<li>Block icons (1 valid):' +
|
||||
' <iconify-icon data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix1 +
|
||||
':account-cash" title="<Cash>!"></iconify-icon>' +
|
||||
' <iconify-icon data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix2 +
|
||||
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
|
||||
'</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
// Scan DOM
|
||||
setRoot(node);
|
||||
scanDOM();
|
||||
|
||||
// Change icon name
|
||||
const icon = node.querySelector('iconify-icon[title]');
|
||||
expect(icon).to.not.be.equal(null);
|
||||
icon.setAttribute('data-icon', '@' + provider + ':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);
|
||||
});
|
||||
|
||||
it('Unattached DOM node', (done) => {
|
||||
const fakeRoot = getNode('scan-dom-unattached');
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
|
||||
// Set fake API hosts to make test reliable
|
||||
addAPIProvider(provider, {
|
||||
resources: ['https://api1.local', 'https://api2.local'],
|
||||
});
|
||||
|
||||
// Load icons after 100ms
|
||||
const data: FakeData = {
|
||||
icons: ['home'],
|
||||
delay: 100,
|
||||
data: {
|
||||
prefix,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
};
|
||||
setFakeData(provider, prefix, data);
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML =
|
||||
'Icon:' +
|
||||
' <span class="iconify" data-icon="@' +
|
||||
provider +
|
||||
':' +
|
||||
prefix +
|
||||
':home"></span>';
|
||||
|
||||
// Set root node, test nodes list
|
||||
setRoot(fakeRoot);
|
||||
|
||||
// Test listRootNodes
|
||||
let nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeRoot);
|
||||
expect(nodes[0].temporary).to.be.equal(false);
|
||||
|
||||
// Scan different node
|
||||
scanElement(node);
|
||||
|
||||
// Test listRootNodes
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(2);
|
||||
expect(nodes[0].node).to.be.equal(fakeRoot);
|
||||
expect(nodes[1].node).to.be.equal(node);
|
||||
expect(nodes[1].temporary).to.be.equal(true);
|
||||
|
||||
// API response should have loaded
|
||||
setTimeout(() => {
|
||||
const elements = node.querySelectorAll('svg.iconify');
|
||||
expect(elements.length).to.be.equal(
|
||||
1,
|
||||
'Expected to find 1 rendered SVG element'
|
||||
);
|
||||
|
||||
// Test nodes list: temporary node should have been removed
|
||||
nodes = listRootNodes();
|
||||
expect(nodes.length).to.be.equal(1);
|
||||
expect(nodes[0].node).to.be.equal(fakeRoot);
|
||||
|
||||
// Done
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
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';
|
||||
|
||||
// Do not observe document.body!
|
||||
Iconify.stopObserving(document.documentElement);
|
||||
|
||||
// Create node to observe
|
||||
const observedNode = getNode('iconify-api');
|
||||
const ignoredNode = getNode('iconify-api');
|
||||
|
||||
Iconify.observe(observedNode);
|
||||
|
||||
observedNode.innerHTML =
|
||||
'<div><p>Testing Iconify with API (should render SVG!)</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>';
|
||||
|
||||
ignoredNode.innerHTML =
|
||||
'<div>This node should not have icons! <span class="iconify-inline" data-icon="mdi:home" style="color: red; box-shadow: 0 0 2px black;"></span>';
|
||||
|
||||
describe('Testing Iconify object with API', () => {
|
||||
it('Rendering icons with API', () => {
|
||||
// Icons should have been replaced by now
|
||||
let list = observedNode.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(0);
|
||||
|
||||
list = observedNode.querySelectorAll('svg.iconify');
|
||||
expect(list.length).to.be.equal(4);
|
||||
|
||||
// Icons in ignored node should not have been replaced
|
||||
list = ignoredNode.querySelectorAll(selector);
|
||||
expect(list.length).to.be.equal(1);
|
||||
|
||||
list = ignoredNode.querySelectorAll('svg.iconify');
|
||||
expect(list.length).to.be.equal(0);
|
||||
});
|
||||
});
|
@ -1,141 +0,0 @@
|
||||
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');
|
||||
|
||||
// Do not observe document.body!
|
||||
Iconify.stopObserving(document.documentElement);
|
||||
|
||||
// Set root node
|
||||
Iconify.observe(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('Check iconExists', () => {
|
||||
expect(Iconify.iconExists(prefix + ':' + 'account')).to.be.equal(true);
|
||||
expect(Iconify.iconExists(prefix + ':' + 'missing')).to.be.equal(false);
|
||||
expect(Iconify.iconExists(prefix + '-123:' + 'missing')).to.be.equal(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('Check listIcons', () => {
|
||||
expect(Iconify.listIcons('', prefix)).to.be.eql([
|
||||
prefix + ':account-box',
|
||||
prefix + ':account-cash',
|
||||
prefix + ':account',
|
||||
prefix + ':home',
|
||||
prefix + ':id-test',
|
||||
]);
|
||||
});
|
||||
|
||||
it('Get SVG node', () => {
|
||||
const node = Iconify.renderSVG(prefix + ':account', {
|
||||
inline: true,
|
||||
});
|
||||
expect(node).to.not.be.equal(null);
|
||||
|
||||
const html = node.outerHTML;
|
||||
expect(html.indexOf('<svg')).to.be.equal(0);
|
||||
|
||||
// Get HTML
|
||||
const html2 = Iconify.renderHTML(prefix + ':account', {
|
||||
inline: true,
|
||||
});
|
||||
expect(html2).to.be.equal(html);
|
||||
|
||||
// Make sure inline attribute was applied
|
||||
expect(html2.indexOf('vertical-align: -0.125em;') === -1).to.be.equal(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('Rendering icons without API', (done) => {
|
||||
node1.innerHTML =
|
||||
'<div><p>Testing Iconify without API (should render SVG!) and with titles</p>' +
|
||||
' <span class="iconify-inline" data-icon="' +
|
||||
prefix +
|
||||
':home" style="color: red; box-shadow: 0 0 2px black;" title="Home Icon"></span>' +
|
||||
' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="' +
|
||||
prefix +
|
||||
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false" title="Account Icon"></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" title="account-box"></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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,126 +0,0 @@
|
||||
import mocha from 'mocha';
|
||||
import chai from 'chai';
|
||||
|
||||
import { getNode } from './node';
|
||||
import Iconify from '@iconify/iconify/lib/iconify.without-api';
|
||||
|
||||
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');
|
||||
|
||||
// Do not observe document.body!
|
||||
Iconify.stopObserving(document.documentElement);
|
||||
|
||||
// Set root node
|
||||
Iconify.observe(node1);
|
||||
|
||||
describe('Testing Iconify object (without API)', () => {
|
||||
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('Check iconExists', () => {
|
||||
expect(Iconify.iconExists(prefix + ':' + 'account')).to.be.equal(true);
|
||||
expect(Iconify.iconExists(prefix + ':' + 'missing')).to.be.equal(false);
|
||||
expect(Iconify.iconExists(prefix + '-123:' + 'missing')).to.be.equal(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('Get SVG node', () => {
|
||||
const node = Iconify.renderSVG(prefix + ':account', {
|
||||
inline: true,
|
||||
});
|
||||
expect(node).to.not.be.equal(null);
|
||||
|
||||
const html = node.outerHTML;
|
||||
expect(html.indexOf('<svg')).to.be.equal(0);
|
||||
|
||||
// Get HTML
|
||||
const html2 = Iconify.renderHTML(prefix + ':account', {
|
||||
inline: true,
|
||||
});
|
||||
expect(html2).to.be.equal(html);
|
||||
});
|
||||
|
||||
it('Rendering icons without API', (done) => {
|
||||
node1.innerHTML =
|
||||
'<div><p>Testing Iconify without API (should render SVG!)</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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,134 +0,0 @@
|
||||
import { QueryModuleResponse } from '@iconify/api-redundancy';
|
||||
import {
|
||||
IconifyAPIIconsQueryParams,
|
||||
IconifyAPIQueryParams,
|
||||
IconifyAPIPrepareIconsQuery,
|
||||
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, Record<string, FakeData[]>> = Object.create(
|
||||
null
|
||||
);
|
||||
|
||||
export function setFakeData(
|
||||
provider: string,
|
||||
prefix: string,
|
||||
item: FakeData
|
||||
): void {
|
||||
if (fakeData[provider] === void 0) {
|
||||
fakeData[provider] = Object.create(null);
|
||||
}
|
||||
const providerFakeData = fakeData[provider];
|
||||
if (providerFakeData[prefix] === void 0) {
|
||||
providerFakeData[prefix] = [];
|
||||
}
|
||||
providerFakeData[prefix].push(item);
|
||||
}
|
||||
|
||||
interface FakeAPIQueryParams extends IconifyAPIIconsQueryParams {
|
||||
data: FakeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare params
|
||||
*/
|
||||
export const prepareQuery: IconifyAPIPrepareIconsQuery = (
|
||||
provider: string,
|
||||
prefix: string,
|
||||
icons: string[]
|
||||
): IconifyAPIIconsQueryParams[] => {
|
||||
// Find items that have query
|
||||
const items: IconifyAPIIconsQueryParams[] = [];
|
||||
let missing = icons.slice(0);
|
||||
|
||||
if (fakeData[provider] === void 0) {
|
||||
fakeData[provider] = Object.create(null);
|
||||
}
|
||||
const providerFakeData = fakeData[provider];
|
||||
|
||||
const type = 'icons';
|
||||
if (providerFakeData[prefix] !== void 0) {
|
||||
providerFakeData[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 = {
|
||||
type,
|
||||
provider,
|
||||
prefix,
|
||||
icons: matches,
|
||||
data: item,
|
||||
};
|
||||
items.push(query);
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load icons
|
||||
*/
|
||||
export const sendQuery: IconifyAPISendQuery = (
|
||||
host: string,
|
||||
params: IconifyAPIQueryParams,
|
||||
callback: QueryModuleResponse
|
||||
): void => {
|
||||
if (params.type !== 'icons') {
|
||||
// Fake API supports only icons
|
||||
callback('abort', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = params.provider;
|
||||
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 - send error (first parameter = undefined)
|
||||
callback('abort', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const sendResponse = () => {
|
||||
console.log(
|
||||
'Sending data for prefix "' +
|
||||
(provider === '' ? '' : '@' + provider + ':') +
|
||||
prefix +
|
||||
'", icons:',
|
||||
icons
|
||||
);
|
||||
callback('success', data.data);
|
||||
};
|
||||
|
||||
if (!data.delay) {
|
||||
sendResponse();
|
||||
} else {
|
||||
setTimeout(sendResponse, data.delay);
|
||||
}
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { addRootNode, listRootNodes } from '@iconify/iconify/lib/modules/root';
|
||||
import { stopObserving } from '@iconify/iconify/lib/modules/observer';
|
||||
import { ObservedNode } from '@iconify/iconify/lib/modules/observed-node';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set root node, remove old nodes
|
||||
*/
|
||||
export function setRoot(node: HTMLElement): ObservedNode {
|
||||
listRootNodes().forEach((node) => {
|
||||
if (typeof node.node !== 'function') {
|
||||
stopObserving(node.node);
|
||||
}
|
||||
});
|
||||
return addRootNode(node);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<!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>
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./tests",
|
||||
"outDir": "./lib",
|
||||
"target": "ES2019",
|
||||
"module": "ESNext",
|
||||
"declaration": false,
|
||||
"sourceMap": false,
|
||||
"strict": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
997
packages/iconify/package-lock.json
generated
997
packages/iconify/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -75,11 +75,13 @@
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/node": "^17.0.22",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.11.0",
|
||||
"jest": "^28.0.0-alpha.7",
|
||||
"jsdom": "^19.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
|
@ -11,24 +11,29 @@ import {
|
||||
} from '@iconify/core/lib/storage/functions';
|
||||
import type { IconifyIconBuildResult } from '@iconify/utils/lib/svg/build';
|
||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||
import { renderIconInPlaceholder } from './modules/render';
|
||||
import { initObserver } from './modules/observer';
|
||||
import { scanDOM, scanElement } from './modules/scanner';
|
||||
|
||||
// Finders
|
||||
import { addFinder } from './modules/finder';
|
||||
import { finder as iconifyFinder } from './finders/iconify';
|
||||
import { addBodyNode } from './modules/root';
|
||||
// import { finder as iconifyIconFinder } from './finders/iconify-icon';
|
||||
import { initObserver } from './observer/index';
|
||||
import { scanDOM, scanElement } from './scanner/index';
|
||||
import { addBodyNode } from './observer/root';
|
||||
import { renderInlineSVG } from './render/svg';
|
||||
|
||||
/**
|
||||
* Generate icon
|
||||
*/
|
||||
function generateIcon(
|
||||
name: string,
|
||||
customisations: IconifyIconCustomisations | undefined,
|
||||
returnString: false | undefined
|
||||
): SVGSVGElement | null;
|
||||
function generateIcon(
|
||||
name: string,
|
||||
customisations: IconifyIconCustomisations | undefined,
|
||||
returnString: true
|
||||
): string | null;
|
||||
function generateIcon(
|
||||
name: string,
|
||||
customisations?: IconifyIconCustomisations,
|
||||
returnString?: boolean
|
||||
): SVGElement | string | null {
|
||||
returnString = false
|
||||
): SVGSVGElement | string | null {
|
||||
// Get icon data
|
||||
const iconData = getIconData(name);
|
||||
if (!iconData) {
|
||||
@ -45,14 +50,18 @@ function generateIcon(
|
||||
);
|
||||
|
||||
// Get data
|
||||
return renderIconInPlaceholder(
|
||||
const result = renderInlineSVG(
|
||||
document.createElement('span'),
|
||||
{
|
||||
name: iconName,
|
||||
name,
|
||||
icon: iconName,
|
||||
customisations: changes,
|
||||
},
|
||||
changes,
|
||||
iconData,
|
||||
returnString
|
||||
) as unknown as SVGElement | string | null;
|
||||
iconData
|
||||
);
|
||||
return returnString
|
||||
? result.outerHTML
|
||||
: (result as unknown as SVGSVGElement);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,10 +192,6 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
|
||||
// Add document.body node
|
||||
addBodyNode();
|
||||
|
||||
// Add finder modules
|
||||
// addFinder(iconifyIconFinder);
|
||||
addFinder(iconifyFinder);
|
||||
|
||||
interface WindowWithIconifyStuff {
|
||||
IconifyPreload?: IconifyJSON[] | IconifyJSON;
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
import type { IconifyFinder } from './interface';
|
||||
import type { IconifyElement } from '../modules/element';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/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 };
|
@ -1,39 +0,0 @@
|
||||
import type { IconifyFinder } from './interface';
|
||||
import type { IconifyElement } from '../modules/element';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/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 };
|
@ -1,161 +0,0 @@
|
||||
import type { IconifyFinder } from './interface';
|
||||
import type { IconifyElement } from '../modules/element';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||
import {
|
||||
flipFromString,
|
||||
alignmentFromString,
|
||||
} from '@iconify/utils/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[] => {
|
||||
const result: string[] = [];
|
||||
classList.forEach((className) => {
|
||||
if (
|
||||
className !== 'iconify' &&
|
||||
className !== '' &&
|
||||
className.slice(0, 9) !== 'iconify--'
|
||||
) {
|
||||
result.push(className);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
export { finder };
|
@ -1,177 +0,0 @@
|
||||
import type { IconifyFinder } from './interface';
|
||||
import type { IconifyElement } from '../modules/element';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||
import {
|
||||
flipFromString,
|
||||
alignmentFromString,
|
||||
} from '@iconify/utils/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[] => {
|
||||
const result: string[] = [];
|
||||
classList.forEach((className) => {
|
||||
if (
|
||||
className !== 'iconify' &&
|
||||
className !== '' &&
|
||||
className.slice(0, 9) !== 'iconify--'
|
||||
) {
|
||||
result.push(className);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
export { finder };
|
@ -1,41 +0,0 @@
|
||||
import type { IconifyElement } from '../modules/element';
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/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;
|
||||
}
|
11
packages/iconify/src/helpers/ready.ts
Normal file
11
packages/iconify/src/helpers/ready.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Execute function when DOM is ready
|
||||
*/
|
||||
export function onReady(callback: () => void): void {
|
||||
const doc = document;
|
||||
if (doc.readyState && doc.readyState !== 'loading') {
|
||||
callback();
|
||||
} else {
|
||||
doc.addEventListener('DOMContentLoaded', callback);
|
||||
}
|
||||
}
|
@ -73,7 +73,7 @@ import {
|
||||
stopObserving,
|
||||
pauseObserver,
|
||||
resumeObserver,
|
||||
} from './modules/observer';
|
||||
} from './observer/index';
|
||||
|
||||
/**
|
||||
* Export required types
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
stopObserving,
|
||||
pauseObserver,
|
||||
resumeObserver,
|
||||
} from './modules/observer';
|
||||
} from './observer/index';
|
||||
|
||||
/**
|
||||
* Export required types
|
||||
|
@ -1,42 +0,0 @@
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import type { IconifyFinder } from '../finders/interface';
|
||||
|
||||
/**
|
||||
* 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;
|
@ -1,148 +0,0 @@
|
||||
import {
|
||||
elementFinderProperty,
|
||||
IconifyElement,
|
||||
elementDataProperty,
|
||||
} from './element';
|
||||
import {
|
||||
IconifyIconName,
|
||||
stringToIcon,
|
||||
validateIcon,
|
||||
} from '@iconify/utils/lib/icon/name';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import type { IconifyFinder } from '../finders/interface';
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
name: IconifyIconName;
|
||||
element?: IconifyElement;
|
||||
finder?: IconifyFinder;
|
||||
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;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Fake interface to test old IE properties
|
||||
interface OldIEElement extends HTMLElement {
|
||||
doScroll?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute function when DOM is ready
|
||||
*/
|
||||
export function onReady(callback: () => void): void {
|
||||
const doc = document;
|
||||
if (
|
||||
doc.readyState === 'complete' ||
|
||||
(doc.readyState !== 'loading' &&
|
||||
!(doc.documentElement as OldIEElement).doScroll)
|
||||
) {
|
||||
callback();
|
||||
} else {
|
||||
doc.addEventListener('DOMContentLoaded', callback);
|
||||
window.addEventListener('load', callback);
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import {
|
||||
mergeCustomisations,
|
||||
defaults,
|
||||
} from '@iconify/utils/lib/customisations';
|
||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||
import { replaceIDs } from '@iconify/utils/lib/svg/id';
|
||||
import type { PlaceholderElement } from './finder';
|
||||
import type { IconifyElement, IconifyElementData } from './element';
|
||||
import { elementDataProperty, elementFinderProperty } from './element';
|
||||
|
||||
/**
|
||||
* Replace element with SVG
|
||||
*/
|
||||
export function renderIconInPlaceholder(
|
||||
placeholder: PlaceholderElement,
|
||||
customisations: IconifyIconCustomisations,
|
||||
iconData: FullIconifyIcon,
|
||||
returnString?: boolean
|
||||
): IconifyElement | string | null {
|
||||
// Create placeholder. Why placeholder? IE11 doesn't support innerHTML method on SVG.
|
||||
let span: HTMLSpanElement;
|
||||
try {
|
||||
span = document.createElement('span');
|
||||
} catch (err) {
|
||||
return returnString ? '' : null;
|
||||
}
|
||||
|
||||
const data = iconToSVG(
|
||||
iconData,
|
||||
mergeCustomisations(defaults, customisations)
|
||||
);
|
||||
|
||||
// Placeholder properties
|
||||
const placeholderElement = placeholder.element;
|
||||
const finder = placeholder.finder;
|
||||
const name = placeholder.name;
|
||||
|
||||
// Get class name
|
||||
const placeholderClassName = placeholderElement
|
||||
? placeholderElement.getAttribute('class')
|
||||
: '';
|
||||
const filteredClassList = finder
|
||||
? finder.classFilter(
|
||||
placeholderClassName ? placeholderClassName.split(/\s+/) : []
|
||||
)
|
||||
: [];
|
||||
const className =
|
||||
'iconify iconify--' +
|
||||
name.prefix +
|
||||
(name.provider === '' ? '' : ' iconify--' + name.provider) +
|
||||
(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" role="img" class="' +
|
||||
className +
|
||||
'">' +
|
||||
replaceIDs(data.body) +
|
||||
'</svg>';
|
||||
|
||||
// Set HTML for placeholder
|
||||
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
|
||||
if (data.inline) {
|
||||
svgStyle.verticalAlign = '-0.125em';
|
||||
}
|
||||
|
||||
// Copy stuff from placeholder
|
||||
if (placeholderElement) {
|
||||
// Copy attributes
|
||||
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
|
||||
const placeholderStyle = placeholderElement.style;
|
||||
for (let i = 0; i < placeholderStyle.length; i++) {
|
||||
const attr = placeholderStyle[i];
|
||||
svgStyle[attr] = placeholderStyle[attr];
|
||||
}
|
||||
}
|
||||
|
||||
// Store finder specific data
|
||||
if (finder) {
|
||||
const elementData: IconifyElementData = {
|
||||
name: name,
|
||||
status: 'loaded',
|
||||
customisations: customisations,
|
||||
};
|
||||
svg[elementDataProperty] = elementData;
|
||||
svg[elementFinderProperty] = finder;
|
||||
}
|
||||
|
||||
// Get result
|
||||
const result = returnString ? span.innerHTML : svg;
|
||||
|
||||
// Replace placeholder
|
||||
if (placeholderElement && placeholderElement.parentNode) {
|
||||
placeholderElement.parentNode.replaceChild(svg, placeholderElement);
|
||||
} else {
|
||||
// Placeholder has no parent? Remove SVG parent as well
|
||||
span.removeChild(svg);
|
||||
}
|
||||
|
||||
// Return new node
|
||||
return result;
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import {
|
||||
getStorage,
|
||||
getIconFromStorage,
|
||||
} from '@iconify/core/lib/storage/storage';
|
||||
import { isPending, loadIcons } from '@iconify/core/lib/api/icons';
|
||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import { findPlaceholders } from './finder';
|
||||
import type { IconifyElementData } from './element';
|
||||
import { elementDataProperty } from './element';
|
||||
import { renderIconInPlaceholder } from './render';
|
||||
import type { ObservedNode } from './observed-node';
|
||||
import {
|
||||
pauseObservingNode,
|
||||
resumeObservingNode,
|
||||
stopObserving,
|
||||
observe,
|
||||
} from './observer';
|
||||
import { findRootNode, listRootNodes } from './root';
|
||||
|
||||
/**
|
||||
* 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 node for placeholders
|
||||
*/
|
||||
export function scanElement(root: HTMLElement): void {
|
||||
// Add temporary node
|
||||
const node = findRootNode(root);
|
||||
if (!node) {
|
||||
scanDOM(
|
||||
{
|
||||
node: root,
|
||||
temporary: true,
|
||||
},
|
||||
true
|
||||
);
|
||||
} else {
|
||||
scanDOM(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan DOM for placeholders
|
||||
*/
|
||||
export function scanDOM(node?: ObservedNode, addTempNode = false): void {
|
||||
scanQueued = false;
|
||||
|
||||
// List of icons to load: [provider][prefix][name] = boolean
|
||||
const iconsToLoad: Record<
|
||||
string,
|
||||
Record<string, Record<string, boolean>>
|
||||
> = Object.create(null);
|
||||
|
||||
// Get placeholders
|
||||
(node ? [node] : listRootNodes()).forEach((node) => {
|
||||
const root = typeof node.node === 'function' ? node.node() : node.node;
|
||||
|
||||
if (!root || !root.querySelectorAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track placeholders
|
||||
let hasPlaceholders = false;
|
||||
|
||||
// Observer
|
||||
let paused = false;
|
||||
|
||||
// Find placeholders
|
||||
findPlaceholders(root).forEach((item) => {
|
||||
const element = item.element;
|
||||
const iconName = item.name;
|
||||
const provider = iconName.provider;
|
||||
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 (
|
||||
isPending({
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
})
|
||||
) {
|
||||
// Pending
|
||||
hasPlaceholders = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check icon
|
||||
const storage = getStorage(provider, prefix);
|
||||
if (storage.icons[name] !== void 0) {
|
||||
// Icon exists - pause observer before replacing placeholder
|
||||
if (!paused && node.observer) {
|
||||
pauseObservingNode(node);
|
||||
paused = true;
|
||||
}
|
||||
|
||||
// Get customisations
|
||||
const customisations =
|
||||
item.customisations !== void 0
|
||||
? item.customisations
|
||||
: item.finder.customisations(element);
|
||||
|
||||
// Render icon
|
||||
renderIconInPlaceholder(
|
||||
item,
|
||||
customisations,
|
||||
getIconFromStorage(storage, name) as FullIconifyIcon
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (storage.missing[name]) {
|
||||
// Mark as missing
|
||||
data = {
|
||||
name: iconName,
|
||||
status: 'missing',
|
||||
customisations: {},
|
||||
};
|
||||
element[elementDataProperty] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPending({ provider, prefix, name })) {
|
||||
// Add icon to loading queue
|
||||
if (iconsToLoad[provider] === void 0) {
|
||||
iconsToLoad[provider] = Object.create(null);
|
||||
}
|
||||
const providerIconsToLoad = iconsToLoad[provider];
|
||||
if (providerIconsToLoad[prefix] === void 0) {
|
||||
providerIconsToLoad[prefix] = Object.create(null);
|
||||
}
|
||||
providerIconsToLoad[prefix][name] = true;
|
||||
}
|
||||
|
||||
// Mark as loading
|
||||
data = {
|
||||
name: iconName,
|
||||
status: 'loading',
|
||||
customisations: {},
|
||||
};
|
||||
element[elementDataProperty] = data;
|
||||
hasPlaceholders = true;
|
||||
});
|
||||
|
||||
// Node stuff
|
||||
if (node.temporary && !hasPlaceholders) {
|
||||
// Remove temporary node
|
||||
stopObserving(root);
|
||||
} else if (addTempNode && hasPlaceholders) {
|
||||
// Add new temporary node
|
||||
observe(root, true);
|
||||
} else if (paused && node.observer) {
|
||||
// Resume observer
|
||||
resumeObservingNode(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Load icons
|
||||
Object.keys(iconsToLoad).forEach((provider) => {
|
||||
const providerIconsToLoad = iconsToLoad[provider];
|
||||
Object.keys(providerIconsToLoad).forEach((prefix) => {
|
||||
loadIcons(
|
||||
Object.keys(providerIconsToLoad[prefix]).map((name) => {
|
||||
const icon: IconifyIconName = {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
};
|
||||
return icon;
|
||||
}),
|
||||
checkPendingIcons
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
import type { IconifyElement } from './element';
|
||||
import { elementFinderProperty } from './element';
|
||||
import type { ObservedNode } from './observed-node';
|
||||
import { elementDataProperty, IconifyElement } from '../scanner/config';
|
||||
import type { ObservedNode } from './types';
|
||||
import {
|
||||
listRootNodes,
|
||||
addRootNode,
|
||||
findRootNode,
|
||||
removeRootNode,
|
||||
} from './root';
|
||||
import { onReady } from './ready';
|
||||
import { onReady } from '../helpers/ready';
|
||||
|
||||
/**
|
||||
* Observer callback function
|
||||
@ -64,7 +63,7 @@ function checkMutations(node: ObservedNode, mutations: MutationRecord[]): void {
|
||||
(item.addedNodes && item.addedNodes.length > 0) ||
|
||||
// Check for icon or placeholder with modified attributes
|
||||
(item.type === 'attributes' &&
|
||||
(item.target as IconifyElement)[elementFinderProperty] !==
|
||||
(item.target as IconifyElement)[elementDataProperty] !==
|
||||
void 0)
|
||||
) {
|
||||
if (!observer.paused) {
|
||||
@ -94,8 +93,8 @@ function startObserver(node: ObservedNode): void {
|
||||
}
|
||||
|
||||
const root = typeof node.node === 'function' ? node.node() : node.node;
|
||||
if (!root) {
|
||||
// document.body is not available yet
|
||||
if (!root || !window) {
|
||||
// document.body is not available yet or window is missing
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,7 +106,9 @@ function startObserver(node: ObservedNode): void {
|
||||
}
|
||||
|
||||
// Create new instance, observe
|
||||
observer.instance = new MutationObserver(checkMutations.bind(null, node));
|
||||
observer.instance = new window.MutationObserver(
|
||||
checkMutations.bind(null, node)
|
||||
);
|
||||
continueObserving(node, root);
|
||||
|
||||
// Scan immediately
|
||||
@ -126,7 +127,7 @@ function startObservers(): void {
|
||||
/**
|
||||
* Stop observer
|
||||
*/
|
||||
function stopObserver(node: ObservedNode): void {
|
||||
export function stopObserver(node: ObservedNode): void {
|
||||
if (!node.observer) {
|
||||
return;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import type { ObservedNode } from './observed-node';
|
||||
import type { ObservedNode } from './types';
|
||||
|
||||
/**
|
||||
* List of root nodes
|
||||
@ -34,7 +34,7 @@ export function addRootNode(
|
||||
return node;
|
||||
}
|
||||
|
||||
// Create item, add it to list, start observer
|
||||
// Create item, add it to list
|
||||
node = {
|
||||
node: root,
|
||||
temporary: autoRemove,
|
||||
@ -61,12 +61,12 @@ export function addBodyNode(): ObservedNode {
|
||||
/**
|
||||
* Remove root node
|
||||
*/
|
||||
export function removeRootNode(root: HTMLElement): void {
|
||||
nodes = nodes.filter((node) => {
|
||||
const element =
|
||||
typeof node.node === 'function' ? node.node() : node.node;
|
||||
return root !== element;
|
||||
});
|
||||
export function removeRootNode(root: HTMLElement | ObservedNode): void {
|
||||
nodes = nodes.filter(
|
||||
(node) =>
|
||||
root !== node &&
|
||||
root !== (typeof node.node === 'function' ? node.node() : node.node)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
44
packages/iconify/src/render/classes.ts
Normal file
44
packages/iconify/src/render/classes.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { IconifyElement } from '../scanner/config';
|
||||
|
||||
/**
|
||||
* Add classes to SVG, removing previously added classes, keeping custom classes
|
||||
*/
|
||||
export function applyClasses(
|
||||
svg: IconifyElement,
|
||||
classes: Set<string>,
|
||||
previouslyAddedClasses: Set<string>,
|
||||
placeholder?: IconifyElement
|
||||
): string[] {
|
||||
const svgClasses = svg.classList;
|
||||
|
||||
// Copy classes from placeholder
|
||||
if (placeholder) {
|
||||
const placeholderClasses = placeholder.classList;
|
||||
Array.from(placeholderClasses).forEach((item) => {
|
||||
svgClasses.add(item);
|
||||
});
|
||||
}
|
||||
|
||||
// Add new classes
|
||||
const addedClasses: string[] = [];
|
||||
classes.forEach((item: string) => {
|
||||
if (!svgClasses.contains(item)) {
|
||||
// Add new class
|
||||
svgClasses.add(item);
|
||||
addedClasses.push(item);
|
||||
} else if (previouslyAddedClasses.has(item)) {
|
||||
// Was added before: keep it
|
||||
addedClasses.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove previously added classes
|
||||
previouslyAddedClasses.forEach((item) => {
|
||||
if (!classes.has(item)) {
|
||||
// Class that was added before, but no longer needed
|
||||
svgClasses.remove(item);
|
||||
}
|
||||
});
|
||||
|
||||
return addedClasses;
|
||||
}
|
31
packages/iconify/src/render/style.ts
Normal file
31
packages/iconify/src/render/style.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type {
|
||||
IconifyElement,
|
||||
IconifyElementChangedStyles,
|
||||
} from '../scanner/config';
|
||||
|
||||
/**
|
||||
* Copy old styles, apply new styles
|
||||
*/
|
||||
export function applyStyle(
|
||||
svg: IconifyElement,
|
||||
styles: Record<string, string>,
|
||||
previouslyAddedStyles?: IconifyElementChangedStyles
|
||||
): IconifyElementChangedStyles {
|
||||
const svgStyle = svg.style;
|
||||
|
||||
// Remove previously added styles
|
||||
(previouslyAddedStyles || []).forEach((prop) => {
|
||||
svgStyle.removeProperty(prop);
|
||||
});
|
||||
|
||||
// Apply new styles, ignoring styles that already exist
|
||||
const appliedStyles: IconifyElementChangedStyles = [];
|
||||
for (const prop in styles) {
|
||||
if (!svgStyle.getPropertyValue(prop)) {
|
||||
appliedStyles.push(prop);
|
||||
svgStyle.setProperty(prop, styles[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
return appliedStyles;
|
||||
}
|
102
packages/iconify/src/render/svg.ts
Normal file
102
packages/iconify/src/render/svg.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||
import { replaceIDs } from '@iconify/utils/lib/svg/id';
|
||||
import {
|
||||
elementDataProperty,
|
||||
IconifyElement,
|
||||
IconifyElementProps,
|
||||
IconifyElementData,
|
||||
} from '../scanner/config';
|
||||
import { applyClasses } from './classes';
|
||||
import { applyStyle } from './style';
|
||||
|
||||
/**
|
||||
* Render icon as inline SVG
|
||||
*/
|
||||
export function renderInlineSVG(
|
||||
element: IconifyElement,
|
||||
props: IconifyElementProps,
|
||||
iconData: FullIconifyIcon
|
||||
): IconifyElement {
|
||||
// Create placeholder. Why placeholder? innerHTML setter on SVG does not work in some environments.
|
||||
let span: HTMLSpanElement;
|
||||
try {
|
||||
span = document.createElement('span');
|
||||
} catch (err) {
|
||||
return element;
|
||||
}
|
||||
|
||||
// Generate data to render
|
||||
const renderData = iconToSVG(iconData, props.customisations);
|
||||
|
||||
// Get old data
|
||||
const oldData = element[elementDataProperty];
|
||||
|
||||
// Generate SVG
|
||||
const html =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img">' +
|
||||
replaceIDs(renderData.body) +
|
||||
'</svg>';
|
||||
span.innerHTML = html;
|
||||
|
||||
// Get SVG element
|
||||
const svg = span.childNodes[0] as IconifyElement;
|
||||
|
||||
// Add attributes
|
||||
const svgAttributes = renderData.attributes as Record<string, string>;
|
||||
Object.keys(svgAttributes).forEach((attr) => {
|
||||
svg.setAttribute(attr, svgAttributes[attr]);
|
||||
});
|
||||
|
||||
const placeholderAttributes = element.attributes;
|
||||
for (let i = 0; i < placeholderAttributes.length; i++) {
|
||||
const item = placeholderAttributes.item(i);
|
||||
const name = item.name;
|
||||
if (name !== 'class' && !svg.hasAttribute(name)) {
|
||||
svg.setAttribute(name, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add classes
|
||||
const classesToAdd: Set<string> = new Set(['iconify']);
|
||||
const iconName = props.icon;
|
||||
['provider', 'prefix'].forEach((attr: keyof typeof iconName) => {
|
||||
if (iconName[attr]) {
|
||||
classesToAdd.add('iconify--' + iconName[attr]);
|
||||
}
|
||||
});
|
||||
const addedClasses = applyClasses(
|
||||
svg,
|
||||
classesToAdd,
|
||||
new Set(oldData && oldData.addedClasses),
|
||||
element
|
||||
);
|
||||
|
||||
// Update style
|
||||
const addedStyles = applyStyle(
|
||||
svg,
|
||||
renderData.inline
|
||||
? {
|
||||
'vertical-align': '-0.125em',
|
||||
}
|
||||
: {},
|
||||
oldData && oldData.addedStyles
|
||||
);
|
||||
|
||||
// Add data to element
|
||||
const newData: IconifyElementData = {
|
||||
...props,
|
||||
isSVG: true,
|
||||
status: 'loaded',
|
||||
addedClasses,
|
||||
addedStyles,
|
||||
};
|
||||
svg[elementDataProperty] = newData;
|
||||
|
||||
// Replace old element
|
||||
if (element.parentNode) {
|
||||
element.parentNode.replaceChild(svg, element);
|
||||
}
|
||||
|
||||
return svg;
|
||||
}
|
24
packages/iconify/src/scanner/compare.ts
Normal file
24
packages/iconify/src/scanner/compare.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { IconifyElementProps } from './config';
|
||||
import { defaults } from '@iconify/utils/lib/customisations';
|
||||
|
||||
/**
|
||||
* Compare props
|
||||
*/
|
||||
export function propsChanged(
|
||||
props1: IconifyElementProps,
|
||||
props2: IconifyElementProps
|
||||
): boolean {
|
||||
if (props1.name !== props2.name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const customisations1 = props1.customisations;
|
||||
const customisations2 = props2.customisations;
|
||||
for (const key in defaults) {
|
||||
if (customisations1[key] !== customisations2[key]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
73
packages/iconify/src/scanner/config.ts
Normal file
73
packages/iconify/src/scanner/config.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import type { IconifyIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
|
||||
/**
|
||||
* Class names
|
||||
*/
|
||||
export const blockClass = 'iconify';
|
||||
export const inlineClass = 'iconify-inline';
|
||||
|
||||
/**
|
||||
* Data used to verify if icon is the same
|
||||
*/
|
||||
export interface IconifyElementProps {
|
||||
// Icon name as string
|
||||
name: string;
|
||||
|
||||
// Icon name as object
|
||||
icon: IconifyIconName;
|
||||
|
||||
// Customisations
|
||||
customisations: Required<IconifyIconCustomisations>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon status
|
||||
*/
|
||||
type IconStatus = 'missing' | 'loading' | 'loaded';
|
||||
|
||||
/**
|
||||
* List of classes added to element
|
||||
*
|
||||
* If class already exists in element, it is not included in list
|
||||
*/
|
||||
export type IconifyElementAddedClasses = string[];
|
||||
|
||||
/**
|
||||
* List of added inline styles
|
||||
*
|
||||
* Style is not changed if custom value is set
|
||||
*/
|
||||
export type IconifyElementChangedStyles = string[];
|
||||
|
||||
/**
|
||||
* Data added to element to keep track of changes
|
||||
*/
|
||||
export interface IconifyElementData extends IconifyElementProps {
|
||||
// Status
|
||||
status: IconStatus;
|
||||
|
||||
// True if SVG has been rendered
|
||||
isSVG?: boolean;
|
||||
|
||||
// List of classes that were added to element on last render
|
||||
addedClasses?: IconifyElementAddedClasses;
|
||||
|
||||
// List of changes to style on last render
|
||||
addedStyles?: IconifyElementChangedStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend Element type to allow TypeScript understand added properties
|
||||
*/
|
||||
interface IconifyElementStoredData {
|
||||
iconifyData?: IconifyElementData;
|
||||
}
|
||||
|
||||
export interface IconifyElement extends HTMLElement, IconifyElementStoredData {}
|
||||
|
||||
/**
|
||||
* Names of properties to add to nodes
|
||||
*/
|
||||
export const elementDataProperty: keyof IconifyElementStoredData =
|
||||
('iconifyData' + Date.now()) as keyof IconifyElementStoredData;
|
57
packages/iconify/src/scanner/find.ts
Normal file
57
packages/iconify/src/scanner/find.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import {
|
||||
blockClass,
|
||||
elementDataProperty,
|
||||
IconifyElement,
|
||||
IconifyElementProps,
|
||||
inlineClass,
|
||||
} from './config';
|
||||
import { getElementProps } from './get-props';
|
||||
|
||||
/**
|
||||
* Selector combining class names and tags
|
||||
*/
|
||||
const selector =
|
||||
'svg.' +
|
||||
blockClass +
|
||||
', i.' +
|
||||
blockClass +
|
||||
', span.' +
|
||||
blockClass +
|
||||
', i.' +
|
||||
inlineClass +
|
||||
', span.' +
|
||||
inlineClass;
|
||||
|
||||
/**
|
||||
* Found nodes
|
||||
*/
|
||||
export type ScannedNodesListItem = {
|
||||
node: IconifyElement;
|
||||
props: IconifyElementProps;
|
||||
};
|
||||
|
||||
export type ScannedNodesList = ScannedNodesListItem[];
|
||||
|
||||
/**
|
||||
* Find all parent nodes in DOM
|
||||
*/
|
||||
export function scanRootNode(root: HTMLElement): ScannedNodesList {
|
||||
const nodes: ScannedNodesList = [];
|
||||
|
||||
root.querySelectorAll(selector).forEach((node: IconifyElement) => {
|
||||
// Get props, ignore SVG rendered outside of SVG framework
|
||||
const props =
|
||||
node[elementDataProperty] || node.tagName.toLowerCase() !== 'svg'
|
||||
? getElementProps(node)
|
||||
: null;
|
||||
|
||||
if (props) {
|
||||
nodes.push({
|
||||
node,
|
||||
props,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return nodes;
|
||||
}
|
107
packages/iconify/src/scanner/get-props.ts
Normal file
107
packages/iconify/src/scanner/get-props.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { stringToIcon } from '@iconify/utils/lib/icon/name';
|
||||
import { defaults } from '@iconify/utils/lib/customisations';
|
||||
import type { IconifyIconCustomisations } from '@iconify/utils/lib/customisations';
|
||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||
import {
|
||||
alignmentFromString,
|
||||
flipFromString,
|
||||
} from '@iconify/utils/lib/customisations/shorthand';
|
||||
import { inlineClass } from './config';
|
||||
import type { IconifyElementProps } from './config';
|
||||
|
||||
/**
|
||||
* Size attributes
|
||||
*/
|
||||
const sizeAttributes: (keyof IconifyIconCustomisations)[] = ['width', 'height'];
|
||||
|
||||
/**
|
||||
* Boolean attributes
|
||||
*/
|
||||
const booleanAttributes: (keyof IconifyIconCustomisations)[] = [
|
||||
'inline',
|
||||
'hFlip',
|
||||
'vFlip',
|
||||
];
|
||||
|
||||
/**
|
||||
* Combined attributes
|
||||
*/
|
||||
type CombinedAtttributeFunction = (
|
||||
customisations: IconifyIconCustomisations,
|
||||
value: string
|
||||
) => void;
|
||||
const combinedAttributes: Record<string, CombinedAtttributeFunction> = {
|
||||
flip: flipFromString,
|
||||
align: alignmentFromString,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
function getBooleanAttribute(value: unknown, key: string): boolean | null {
|
||||
if (value === key || value === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value === '' || value === 'false') {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element properties from HTML element
|
||||
*/
|
||||
export function getElementProps(element: Element): IconifyElementProps | null {
|
||||
// Get icon name
|
||||
const name = element.getAttribute('data-icon');
|
||||
const icon = typeof name === 'string' && stringToIcon(name, true);
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get defaults
|
||||
const customisations = {
|
||||
...defaults,
|
||||
};
|
||||
|
||||
// Get inline status
|
||||
customisations.inline =
|
||||
element.classList && element.classList.contains(inlineClass);
|
||||
|
||||
// Get dimensions
|
||||
sizeAttributes.forEach((attr) => {
|
||||
const value = element.getAttribute('data-' + attr);
|
||||
if (value) {
|
||||
customisations[attr as 'width'] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Get rotation
|
||||
const rotation = element.getAttribute('data-rotate');
|
||||
if (typeof rotation === 'string') {
|
||||
customisations.rotate = rotateFromString(rotation);
|
||||
}
|
||||
|
||||
// Get alignment and transformations shorthand attributes
|
||||
for (const attr in combinedAttributes) {
|
||||
const value = element.getAttribute('data-' + attr);
|
||||
if (typeof value === 'string') {
|
||||
combinedAttributes[attr](customisations, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Boolean attributes
|
||||
booleanAttributes.forEach((attr) => {
|
||||
const key = 'data-' + attr;
|
||||
const value = getBooleanAttribute(element.getAttribute(key), key);
|
||||
if (typeof value === 'boolean') {
|
||||
customisations[attr as 'inline'] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
icon,
|
||||
customisations,
|
||||
};
|
||||
}
|
230
packages/iconify/src/scanner/index.ts
Normal file
230
packages/iconify/src/scanner/index.ts
Normal file
@ -0,0 +1,230 @@
|
||||
import {
|
||||
getIconFromStorage,
|
||||
getStorage,
|
||||
} from '@iconify/core/lib/storage/storage';
|
||||
import { isPending, loadIcons } from '@iconify/core/lib/api/icons';
|
||||
import { findRootNode, listRootNodes } from '../observer/root';
|
||||
import type { ObservedNode } from '../observer/types';
|
||||
import { propsChanged } from './compare';
|
||||
import {
|
||||
elementDataProperty,
|
||||
IconifyElement,
|
||||
IconifyElementData,
|
||||
IconifyElementProps,
|
||||
} from './config';
|
||||
import { scanRootNode } from './find';
|
||||
import type { IconifyIconName } from '../iconify';
|
||||
import type { FullIconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import { renderInlineSVG } from '../render/svg';
|
||||
import {
|
||||
observe,
|
||||
pauseObservingNode,
|
||||
resumeObservingNode,
|
||||
stopObserving,
|
||||
} from '../observer';
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan node for placeholders
|
||||
*/
|
||||
export function scanDOM(rootNode?: ObservedNode, addTempNode = false): void {
|
||||
// List of icons to load: [provider][prefix] = Set<name>
|
||||
const iconsToLoad: Record<
|
||||
string,
|
||||
Record<string, Set<string>>
|
||||
> = Object.create(null);
|
||||
|
||||
/**
|
||||
* Get status based on icon
|
||||
*/
|
||||
interface GetIconResult {
|
||||
status: IconifyElementData['status'];
|
||||
icon?: FullIconifyIcon;
|
||||
}
|
||||
function getIcon(icon: IconifyIconName, load: boolean): GetIconResult {
|
||||
const { provider, prefix, name } = icon;
|
||||
const storage = getStorage(provider, prefix);
|
||||
|
||||
if (storage.icons[name]) {
|
||||
return {
|
||||
status: 'loaded',
|
||||
icon: getIconFromStorage(storage, name),
|
||||
};
|
||||
}
|
||||
|
||||
if (storage.missing[name]) {
|
||||
return {
|
||||
status: 'missing',
|
||||
};
|
||||
}
|
||||
|
||||
if (load && !isPending(icon)) {
|
||||
const providerIconsToLoad =
|
||||
iconsToLoad[provider] ||
|
||||
(iconsToLoad[provider] = Object.create(null));
|
||||
const set =
|
||||
providerIconsToLoad[prefix] ||
|
||||
(providerIconsToLoad[prefix] = new Set());
|
||||
set.add(name);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'loading',
|
||||
};
|
||||
}
|
||||
|
||||
// Parse all root nodes
|
||||
(rootNode ? [rootNode] : listRootNodes()).forEach((observedNode) => {
|
||||
const root =
|
||||
typeof observedNode.node === 'function'
|
||||
? observedNode.node()
|
||||
: observedNode.node;
|
||||
|
||||
if (!root || !root.querySelectorAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track placeholders
|
||||
let hasPlaceholders = false;
|
||||
|
||||
// Observer
|
||||
let paused = false;
|
||||
|
||||
/**
|
||||
* Render icon
|
||||
*/
|
||||
function render(
|
||||
element: IconifyElement,
|
||||
props: IconifyElementProps,
|
||||
iconData: FullIconifyIcon
|
||||
) {
|
||||
if (!paused) {
|
||||
paused = true;
|
||||
pauseObservingNode(observedNode);
|
||||
}
|
||||
renderInlineSVG(element, props, iconData);
|
||||
}
|
||||
|
||||
// Find all elements
|
||||
scanRootNode(root).forEach(({ node, props }) => {
|
||||
// Check if item already has props
|
||||
const oldData = node[elementDataProperty];
|
||||
if (!oldData) {
|
||||
// New icon without data
|
||||
const { status, icon } = getIcon(props.icon, true);
|
||||
if (icon) {
|
||||
// Ready to render!
|
||||
render(node, props, icon);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loading or missing
|
||||
hasPlaceholders = hasPlaceholders || status === 'loading';
|
||||
node[elementDataProperty] = {
|
||||
...props,
|
||||
status,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Previously found icon
|
||||
let item: GetIconResult;
|
||||
if (!propsChanged(oldData, props)) {
|
||||
// Props have not changed. Check status
|
||||
const oldStatus = oldData.status;
|
||||
if (oldStatus !== 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
item = getIcon(props.icon, false);
|
||||
if (!item.icon) {
|
||||
// Nothing to render
|
||||
oldData.status = item.status;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Properties have changed: load icon if name has changed
|
||||
item = getIcon(props.icon, oldData.name !== props.name);
|
||||
if (!item.icon) {
|
||||
// Cannot render icon: update status and props
|
||||
hasPlaceholders =
|
||||
hasPlaceholders || item.status === 'loading';
|
||||
Object.assign(oldData, {
|
||||
...props,
|
||||
status: item.status,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-render icon
|
||||
render(node, props, item.icon);
|
||||
});
|
||||
|
||||
// Observed node stuff
|
||||
if (observedNode.temporary && !hasPlaceholders) {
|
||||
// Remove temporary node
|
||||
stopObserving(root);
|
||||
} else if (addTempNode && hasPlaceholders) {
|
||||
// Add new temporary node
|
||||
observe(root, true);
|
||||
} else if (paused && observedNode.observer) {
|
||||
// Resume observer
|
||||
resumeObservingNode(observedNode);
|
||||
}
|
||||
});
|
||||
|
||||
// Load icons
|
||||
for (const provider in iconsToLoad) {
|
||||
const providerIconsToLoad = iconsToLoad[provider];
|
||||
for (const prefix in providerIconsToLoad) {
|
||||
const set = providerIconsToLoad[prefix];
|
||||
loadIcons(
|
||||
Array.from(set).map((name) => ({
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
})),
|
||||
checkPendingIcons
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan node for placeholders
|
||||
*/
|
||||
export function scanElement(root: HTMLElement): void {
|
||||
// Add temporary node
|
||||
const node = findRootNode(root);
|
||||
if (!node) {
|
||||
scanDOM(
|
||||
{
|
||||
node: root,
|
||||
temporary: true,
|
||||
},
|
||||
true
|
||||
);
|
||||
} else {
|
||||
scanDOM(node);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import Iconify from '../';
|
||||
|
||||
describe('Testing Iconify API functions with Node.js', () => {
|
||||
it('Cache functions', () => {
|
||||
// All functions should fail, not without throwing exceptions
|
||||
expect(Iconify.disableCache('all')).toBeUndefined();
|
||||
expect(Iconify.enableCache('all')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Adding API provider', () => {
|
||||
// Add dummy provider. Should not throw exceptions and return true on success
|
||||
expect(
|
||||
Iconify.addAPIProvider('test', {
|
||||
resources: ['http://localhost'],
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
500
packages/iconify/tests/element-props-test.ts
Normal file
500
packages/iconify/tests/element-props-test.ts
Normal file
@ -0,0 +1,500 @@
|
||||
import { cleanupGlobals, setupDOM } from './helpers';
|
||||
import { getElementProps } from '../src/scanner/get-props';
|
||||
import { propsChanged } from '../src/scanner/compare';
|
||||
import { defaults } from '@iconify/utils/lib/customisations';
|
||||
|
||||
describe('Testing element properties', () => {
|
||||
beforeEach(() => {
|
||||
setupDOM('');
|
||||
});
|
||||
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Icon name', () => {
|
||||
const element = document.createElement('span');
|
||||
expect(getElementProps(element)).toBeNull();
|
||||
|
||||
// Set name
|
||||
element.setAttribute('data-icon', 'mdi:home');
|
||||
const props1 = getElementProps(element);
|
||||
expect(props1).toEqual({
|
||||
name: 'mdi:home',
|
||||
icon: {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
},
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
||||
// More complex name
|
||||
element.setAttribute('data-icon', '@custom-api:icon-prefix:icon-name');
|
||||
const props2 = getElementProps(element);
|
||||
expect(props2).toEqual({
|
||||
name: '@custom-api:icon-prefix:icon-name',
|
||||
icon: {
|
||||
provider: 'custom-api',
|
||||
prefix: 'icon-prefix',
|
||||
name: 'icon-name',
|
||||
},
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
expect(propsChanged(props1, props2)).toBe(true);
|
||||
|
||||
// Short name
|
||||
element.setAttribute('data-icon', 'mdi-home');
|
||||
const props3 = getElementProps(element);
|
||||
expect(props3).toEqual({
|
||||
name: 'mdi-home',
|
||||
icon: {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
},
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
expect(propsChanged(props1, props3)).toBe(true);
|
||||
expect(propsChanged(props2, props3)).toBe(true);
|
||||
|
||||
// Invalid name
|
||||
element.setAttribute('data-icon', 'home');
|
||||
expect(getElementProps(element)).toBeNull();
|
||||
});
|
||||
|
||||
it('Inline icon', () => {
|
||||
const element = document.createElement('span');
|
||||
|
||||
// Set icon name
|
||||
const name = 'mdi:home';
|
||||
const icon = {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
};
|
||||
element.setAttribute('data-icon', name);
|
||||
|
||||
// Block: default
|
||||
const props1Block = getElementProps(element);
|
||||
expect(props1Block).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Inline: set via attribute
|
||||
element.setAttribute('data-inline', 'true');
|
||||
const props2Inline = getElementProps(element);
|
||||
expect(props2Inline).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: true,
|
||||
},
|
||||
});
|
||||
expect(propsChanged(props1Block, props2Inline)).toBe(true);
|
||||
|
||||
// Block: set via attribute
|
||||
element.setAttribute('data-inline', 'false');
|
||||
const props3Block = getElementProps(element);
|
||||
expect(props3Block).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: false,
|
||||
},
|
||||
});
|
||||
expect(propsChanged(props1Block, props3Block)).toBe(false);
|
||||
|
||||
// Inline: set via class
|
||||
element.removeAttribute('data-inline');
|
||||
element.className = 'iconify-inline';
|
||||
const props4Inline = getElementProps(element);
|
||||
expect(props4Inline).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: true,
|
||||
},
|
||||
});
|
||||
expect(propsChanged(props1Block, props4Inline)).toBe(true);
|
||||
expect(propsChanged(props2Inline, props4Inline)).toBe(false);
|
||||
|
||||
// Block: set via class
|
||||
element.className = 'iconify';
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Inline: set via attribute, overriding class
|
||||
element.className = 'iconify';
|
||||
element.setAttribute('data-inline', 'true');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Block: set via attribute, overriding class
|
||||
element.className = 'iconify-inline';
|
||||
element.setAttribute('data-inline', 'false');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
inline: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Dimensions', () => {
|
||||
const element = document.createElement('span');
|
||||
|
||||
// Set icon name
|
||||
const name = 'mdi:home';
|
||||
const icon = {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
};
|
||||
element.setAttribute('data-icon', name);
|
||||
|
||||
// Default
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
width: null,
|
||||
height: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Set width
|
||||
element.setAttribute('data-width', '200');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
width: '200',
|
||||
height: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Set height
|
||||
element.setAttribute('data-height', '1em');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
width: '200',
|
||||
height: '1em',
|
||||
},
|
||||
});
|
||||
|
||||
// Empty width
|
||||
element.setAttribute('data-width', '');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
width: null,
|
||||
height: '1em',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Rotation', () => {
|
||||
const element = document.createElement('span');
|
||||
|
||||
// Set icon name
|
||||
const name = 'mdi:home';
|
||||
const icon = {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
};
|
||||
element.setAttribute('data-icon', name);
|
||||
|
||||
// Default
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
rotate: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 90deg
|
||||
element.setAttribute('data-rotate', '90deg');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
rotate: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 180deg
|
||||
element.setAttribute('data-rotate', '2');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
rotate: 2,
|
||||
},
|
||||
});
|
||||
|
||||
// 270deg
|
||||
element.setAttribute('data-rotate', '75%');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
rotate: 3,
|
||||
},
|
||||
});
|
||||
|
||||
// 270deg
|
||||
element.setAttribute('data-rotate', '-90deg');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
rotate: 3,
|
||||
},
|
||||
});
|
||||
|
||||
// Invalid values or 0 deg
|
||||
element.setAttribute('data-rotate', '45deg');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
||||
element.setAttribute('data-rotate', '360deg');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
||||
element.setAttribute('data-rotate', 'true');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
||||
element.setAttribute('data-rotate', '-100%');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Flip', () => {
|
||||
const element = document.createElement('span');
|
||||
|
||||
// Set icon name
|
||||
const name = 'mdi:home';
|
||||
const icon = {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
};
|
||||
element.setAttribute('data-icon', name);
|
||||
|
||||
// Default
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Horizontal
|
||||
element.setAttribute('data-flip', 'horizontal');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hFlip: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Both
|
||||
element.setAttribute('data-vFlip', 'true');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hFlip: true,
|
||||
vFlip: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Vertical
|
||||
element.removeAttribute('data-vFlip');
|
||||
element.setAttribute('data-flip', 'vertical');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
vFlip: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Overwriting shorthand attr
|
||||
element.setAttribute('data-vFlip', 'false');
|
||||
element.setAttribute('data-flip', 'vertical');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
|
||||
// Both
|
||||
element.removeAttribute('data-vFlip');
|
||||
element.setAttribute('data-flip', 'vertical,horizontal');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hFlip: true,
|
||||
vFlip: true,
|
||||
},
|
||||
});
|
||||
|
||||
// None
|
||||
element.setAttribute('data-vFlip', 'false');
|
||||
element.setAttribute('data-hFlip', 'false');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Alignment', () => {
|
||||
const element = document.createElement('span');
|
||||
|
||||
// Set icon name
|
||||
const name = 'mdi:home';
|
||||
const icon = {
|
||||
provider: '',
|
||||
prefix: 'mdi',
|
||||
name: 'home',
|
||||
};
|
||||
element.setAttribute('data-icon', name);
|
||||
|
||||
// Default
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hAlign: 'center',
|
||||
vAlign: 'middle',
|
||||
slice: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Horizontal
|
||||
element.setAttribute('data-align', 'left');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hAlign: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
element.setAttribute('data-align', 'right,meet');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hAlign: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
// Vertical, slice
|
||||
element.setAttribute('data-align', 'center,top,slice');
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
vAlign: 'top',
|
||||
slice: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Overrides, spaces
|
||||
element.setAttribute(
|
||||
'data-align',
|
||||
'left right top middle meet\t slice'
|
||||
);
|
||||
expect(getElementProps(element)).toEqual({
|
||||
name,
|
||||
icon,
|
||||
customisations: {
|
||||
...defaults,
|
||||
hAlign: 'right',
|
||||
slice: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
130
packages/iconify/tests/helpers.ts
Normal file
130
packages/iconify/tests/helpers.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { mockAPIModule, mockAPIData } from '@iconify/core/lib/api/modules/mock';
|
||||
import { addAPIProvider } from '@iconify/core/lib/api/config';
|
||||
import { setAPIModule } from '@iconify/core/lib/api/modules';
|
||||
import { removeRootNode, listRootNodes } from '../src/observer/root';
|
||||
import { onReady } from '../src/helpers/ready';
|
||||
import { stopObserver, stopObserving } from '../src/observer';
|
||||
|
||||
/**
|
||||
* Generate next prefix
|
||||
*/
|
||||
let counter = 0;
|
||||
export const nextPrefix = () => 'mock-' + counter++;
|
||||
|
||||
/**
|
||||
* Set mock API module for provider
|
||||
*/
|
||||
export function fakeAPI(provider: string) {
|
||||
// Set API module for provider
|
||||
addAPIProvider(provider, {
|
||||
resources: ['http://localhost'],
|
||||
});
|
||||
setAPIModule(provider, mockAPIModule);
|
||||
}
|
||||
|
||||
export { mockAPIData };
|
||||
|
||||
/**
|
||||
* Async version of onReady()
|
||||
*/
|
||||
export function waitDOMReady() {
|
||||
return new Promise((fulfill) => {
|
||||
onReady(() => {
|
||||
fulfill(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout
|
||||
*
|
||||
* Can chain multiple setTimeout by adding multiple 0 delays
|
||||
*/
|
||||
export function nextTick(delays: number[] = [0]) {
|
||||
return new Promise((fulfill) => {
|
||||
function next() {
|
||||
if (!delays.length) {
|
||||
fulfill(undefined);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
next();
|
||||
}, delays.shift());
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout, until condition is met
|
||||
*/
|
||||
type WaitUntilCheck = () => boolean;
|
||||
export function awaitUntil(callback: WaitUntilCheck, maxDelay = 1000) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
const start = Date.now();
|
||||
|
||||
function next() {
|
||||
setTimeout(() => {
|
||||
if (callback()) {
|
||||
fulfill(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Date.now() - start > maxDelay) {
|
||||
reject('Timed out');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JSDOM instance, overwrite globals
|
||||
*/
|
||||
export function setupDOM(html: string): JSDOM {
|
||||
const dom = new JSDOM(html);
|
||||
(global as unknown as Record<string, unknown>).window = dom.window;
|
||||
(global as unknown as Record<string, unknown>).document =
|
||||
global.window.document;
|
||||
return dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete temporary globals
|
||||
*/
|
||||
export function cleanupGlobals() {
|
||||
delete global.window;
|
||||
delete global.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state
|
||||
*/
|
||||
export function resetState() {
|
||||
// Reset root nodes list and stop all observers
|
||||
listRootNodes().forEach((node) => {
|
||||
stopObserver(node);
|
||||
removeRootNode(node);
|
||||
});
|
||||
|
||||
// Remove globals
|
||||
cleanupGlobals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap HTML
|
||||
*/
|
||||
export function wrapHTML(content: string): string {
|
||||
return `<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import Iconify, { IconifyIconName } from '../';
|
||||
import { mockAPIModule, mockAPIData } from '@iconify/core/lib/api/modules/mock';
|
||||
|
||||
// API provider and prefix for test
|
||||
const provider = 'mock-api';
|
||||
const prefix = 'prefix';
|
||||
|
||||
// Set API module for provider
|
||||
Iconify.addAPIProvider(provider, {
|
||||
resources: ['http://localhost'],
|
||||
});
|
||||
Iconify._api.setAPIModule(provider, mockAPIModule);
|
||||
|
||||
// Set data
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
});
|
||||
|
||||
// Test
|
||||
describe('Testing loadIcons() with Node.js', () => {
|
||||
it('Load icons from API', (done) => {
|
||||
const name = 'home';
|
||||
const fullName = '@' + provider + ':' + prefix + ':' + name;
|
||||
|
||||
Iconify.loadIcons([fullName], (loaded, missing) => {
|
||||
// Check callback data
|
||||
expect(missing).toEqual([]);
|
||||
const icon: IconifyIconName = {
|
||||
provider,
|
||||
prefix,
|
||||
name,
|
||||
};
|
||||
expect(loaded).toEqual([icon]);
|
||||
|
||||
// Check if icon exists
|
||||
expect(Iconify.iconExists(fullName)).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
41
packages/iconify/tests/mock-api-test.ts
Normal file
41
packages/iconify/tests/mock-api-test.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { iconExists } from '@iconify/core/lib/storage/functions';
|
||||
import { loadIcon } from '@iconify/core/lib/api/icons';
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import { fakeAPI, nextPrefix, mockAPIData } from './helpers';
|
||||
|
||||
describe('Testing mock API', () => {
|
||||
it('Setting up API', async () => {
|
||||
// Set config
|
||||
const provider = nextPrefix();
|
||||
const prefix = nextPrefix();
|
||||
fakeAPI(provider);
|
||||
|
||||
// Mock data
|
||||
const name = 'mock-test';
|
||||
const iconName = `@${provider}:${prefix}:${name}`;
|
||||
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
[name]: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Check if icon has been loaded
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
|
||||
// Load icon
|
||||
const data = await loadIcon(iconName);
|
||||
expect(data).toEqual({
|
||||
...iconDefaults,
|
||||
body: '<g />',
|
||||
});
|
||||
});
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import Iconify from '../';
|
||||
|
||||
describe('Testing Iconify observer functions with Node.js', () => {
|
||||
it('Observer functions', () => {
|
||||
// All functions should fail, not without throwing exceptions
|
||||
expect(Iconify.scan()).toBeUndefined();
|
||||
expect(Iconify.pauseObserver()).toBeUndefined();
|
||||
expect(Iconify.resumeObserver()).toBeUndefined();
|
||||
|
||||
// Cannot test observe() and stopObserving() because they require DOM node as parameter
|
||||
});
|
||||
});
|
297
packages/iconify/tests/observing-changes-test.ts
Normal file
297
packages/iconify/tests/observing-changes-test.ts
Normal file
@ -0,0 +1,297 @@
|
||||
import { iconExists } from '@iconify/core/lib/storage/functions';
|
||||
import {
|
||||
fakeAPI,
|
||||
nextPrefix,
|
||||
setupDOM,
|
||||
waitDOMReady,
|
||||
resetState,
|
||||
mockAPIData,
|
||||
awaitUntil,
|
||||
nextTick,
|
||||
} from './helpers';
|
||||
import { addBodyNode } from '../src/observer/root';
|
||||
import { scanDOM } from '../src/scanner/index';
|
||||
import { elementDataProperty } from '../src/scanner/config';
|
||||
import { initObserver } from '../src/observer';
|
||||
|
||||
describe('Observing DOM changes', () => {
|
||||
const provider = nextPrefix();
|
||||
|
||||
beforeAll(() => {
|
||||
fakeAPI(provider);
|
||||
});
|
||||
|
||||
afterEach(resetState);
|
||||
|
||||
it('Loading icon, transforming icon', async () => {
|
||||
const prefix = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix}:home`;
|
||||
|
||||
// Add icon with API
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Check HTML and data
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
|
||||
// Wait for re-render
|
||||
const placeholder = document.body.childNodes[0];
|
||||
await awaitUntil(() => document.body.childNodes[0] !== placeholder);
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" class="iconify iconify--${provider} iconify--${prefix}"><g></g></svg>`
|
||||
);
|
||||
const svg = document.body.childNodes[0] as SVGSVGElement;
|
||||
const svgData = svg[elementDataProperty];
|
||||
expect(svgData.status).toBe('loaded');
|
||||
expect(svgData.isSVG).toBe(true);
|
||||
expect(svgData.name).toEqual(iconName);
|
||||
|
||||
// Rotate icon
|
||||
svg.setAttribute('data-rotate', '90deg');
|
||||
|
||||
// Wait for re-render
|
||||
await awaitUntil(
|
||||
() => document.body.innerHTML.indexOf('transform=') !== -1
|
||||
);
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" data-rotate="90deg" class="iconify iconify--${provider} iconify--${prefix}"><g transform="rotate(90 8 8)"><g></g></g></svg>`
|
||||
);
|
||||
});
|
||||
|
||||
it('Changing icon name after rendering first icon', async () => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix1}:home`;
|
||||
const iconName2 = `@${provider}:${prefix2}:arrow`;
|
||||
let sendSecondIcon = null;
|
||||
|
||||
// Add icon with API
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
expect(iconExists(iconName2)).toBe(false);
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix: prefix1,
|
||||
response: {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix: prefix2,
|
||||
response: {
|
||||
prefix: prefix2,
|
||||
icons: {
|
||||
arrow: {
|
||||
body: '<path d="M0 0v2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: (send) => {
|
||||
sendSecondIcon = send;
|
||||
},
|
||||
});
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Check HTML and data
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
|
||||
// Wait for re-render
|
||||
const placeholder = document.body.childNodes[0];
|
||||
await awaitUntil(() => document.body.childNodes[0] !== placeholder);
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" class="iconify iconify--${provider} iconify--${prefix1}"><g></g></svg>`
|
||||
);
|
||||
const svg = document.body.childNodes[0] as SVGSVGElement;
|
||||
const svgData = svg[elementDataProperty];
|
||||
expect(svgData.status).toBe('loaded');
|
||||
expect(svgData.isSVG).toBe(true);
|
||||
expect(svgData.name).toEqual(iconName);
|
||||
|
||||
// Chang icon name
|
||||
svg.setAttribute('data-icon', iconName2);
|
||||
|
||||
// Wait for DOM to be scanned again and API query to be sent
|
||||
await awaitUntil(() => sendSecondIcon !== null);
|
||||
|
||||
// SVG should not have been replaced yet, but data should match new icon
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName2}" class="iconify iconify--${provider} iconify--${prefix1}"><g></g></svg>`
|
||||
);
|
||||
expect(document.body.childNodes[0]).toBe(svg);
|
||||
expect(svgData.status).toBe('loading');
|
||||
expect(svgData.isSVG).toBe(true);
|
||||
expect(svgData.name).toEqual(iconName2);
|
||||
|
||||
// Send API query
|
||||
sendSecondIcon();
|
||||
|
||||
// Wait for re-render
|
||||
await awaitUntil(
|
||||
() => document.body.innerHTML.indexOf('M0 0v2') !== -1
|
||||
);
|
||||
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="${iconName2}" class="iconify iconify--${provider} iconify--${prefix2}"><path d="M0 0v2"></path></svg>`
|
||||
);
|
||||
});
|
||||
|
||||
it('Changing icon name while loading first icon', async () => {
|
||||
const prefix1 = nextPrefix();
|
||||
const prefix2 = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix1}:home`;
|
||||
const iconName2 = `@${provider}:${prefix2}:arrow`;
|
||||
let sendFirstIcon = null;
|
||||
let sendSecondIcon = null;
|
||||
|
||||
// Add icon with API
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
expect(iconExists(iconName2)).toBe(false);
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix: prefix1,
|
||||
response: {
|
||||
prefix: prefix1,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: (send) => {
|
||||
sendFirstIcon = send;
|
||||
},
|
||||
});
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix: prefix2,
|
||||
response: {
|
||||
prefix: prefix2,
|
||||
icons: {
|
||||
arrow: {
|
||||
body: '<path d="M0 0v2" />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: (send) => {
|
||||
sendSecondIcon = send;
|
||||
},
|
||||
});
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
initObserver(scanDOM);
|
||||
|
||||
// Check HTML and data
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
|
||||
// Wait for DOM to be scanned again and API query to be sent
|
||||
await awaitUntil(() => sendFirstIcon !== null);
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
const placeholder = document.body.childNodes[0] as HTMLSpanElement;
|
||||
const placeholderData = placeholder[elementDataProperty];
|
||||
expect(placeholderData.status).toBe('loading');
|
||||
expect(placeholderData.isSVG).toBeFalsy();
|
||||
expect(placeholderData.name).toEqual(iconName);
|
||||
|
||||
// Chang icon name
|
||||
placeholder.setAttribute('data-icon', iconName2);
|
||||
|
||||
// Wait for DOM to be scanned again and API query to be sent
|
||||
await awaitUntil(() => sendSecondIcon !== null);
|
||||
|
||||
// SVG should not have been rendered yet, but data should match new icon
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName2}"></span>`
|
||||
);
|
||||
expect(document.body.childNodes[0]).toBe(placeholder);
|
||||
expect(placeholderData.status).toBe('loading');
|
||||
expect(placeholderData.isSVG).toBeFalsy();
|
||||
expect(placeholderData.name).toEqual(iconName2);
|
||||
|
||||
// Send first API query
|
||||
sendFirstIcon();
|
||||
await awaitUntil(() => iconExists(iconName));
|
||||
|
||||
// Wait a bit more
|
||||
await nextTick([0, 0, 0]);
|
||||
|
||||
// Nothing should have changed
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName2}"></span>`
|
||||
);
|
||||
expect(document.body.childNodes[0]).toBe(placeholder);
|
||||
expect(placeholderData.status).toBe('loading');
|
||||
expect(placeholderData.isSVG).toBeFalsy();
|
||||
expect(placeholderData.name).toEqual(iconName2);
|
||||
|
||||
// Send second API query
|
||||
sendSecondIcon();
|
||||
|
||||
// Wait for re-render
|
||||
await awaitUntil(
|
||||
() => document.body.innerHTML.indexOf('M0 0v2') !== -1
|
||||
);
|
||||
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="${iconName2}" class="iconify iconify--${provider} iconify--${prefix2}"><path d="M0 0v2"></path></svg>`
|
||||
);
|
||||
});
|
||||
});
|
240
packages/iconify/tests/re-render-node-test.ts
Normal file
240
packages/iconify/tests/re-render-node-test.ts
Normal file
@ -0,0 +1,240 @@
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import { cleanupGlobals, setupDOM, waitDOMReady } from './helpers';
|
||||
import { scanRootNode } from '../src/scanner/find';
|
||||
import { renderInlineSVG } from '../src/render/svg';
|
||||
import type { IconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import { elementDataProperty, IconifyElement } from '../src/scanner/config';
|
||||
|
||||
describe('Testing re-rendering nodes', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
type TestIconCallback = (svg: IconifyElement) => IconifyIcon;
|
||||
|
||||
const testIcon = async (
|
||||
placeholder: string,
|
||||
data: IconifyIcon,
|
||||
expected1: string,
|
||||
callback1: TestIconCallback,
|
||||
expected2: string,
|
||||
callback2?: TestIconCallback,
|
||||
expected3?: string
|
||||
): Promise<IconifyElement> => {
|
||||
setupDOM(placeholder);
|
||||
|
||||
await waitDOMReady();
|
||||
|
||||
function scan(expected: string): IconifyElement {
|
||||
// Find node
|
||||
const root = document.body;
|
||||
const items = scanRootNode(root);
|
||||
|
||||
expect(items.length).toBe(1);
|
||||
|
||||
// Get node and render it
|
||||
const { node, props } = items[0];
|
||||
const svg = renderInlineSVG(node, props, {
|
||||
...iconDefaults,
|
||||
...data,
|
||||
});
|
||||
|
||||
// Find SVG in DOM
|
||||
expect(root.childNodes.length).toBe(1);
|
||||
const expectedSVG = root.childNodes[0];
|
||||
expect(expectedSVG).toBe(svg);
|
||||
|
||||
// Get HTML
|
||||
const html = root.innerHTML;
|
||||
expect(html).toBe(expected);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
// Initial scan
|
||||
const svg1 = scan(expected1);
|
||||
|
||||
// Change element
|
||||
data = callback1(svg1);
|
||||
|
||||
// Scan again
|
||||
const svg2 = scan(expected2);
|
||||
|
||||
if (!callback2) {
|
||||
return svg2;
|
||||
}
|
||||
|
||||
// Change element again
|
||||
data = callback2(svg2);
|
||||
|
||||
// Scan DOM and return result
|
||||
return scan(expected3);
|
||||
};
|
||||
|
||||
it('Changing content', async () => {
|
||||
const svg = await testIcon(
|
||||
'<span class="iconify" data-icon="mdi:home"></span>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" class="iconify iconify--mdi"><g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
|
||||
// Change icon name and size
|
||||
svg.setAttribute('data-icon', 'mdi-light:home-outline');
|
||||
svg.setAttribute('data-height', 'auto');
|
||||
|
||||
return {
|
||||
body: '<path d="" />',
|
||||
width: 32,
|
||||
height: 32,
|
||||
};
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" data-icon="mdi-light:home-outline" data-height="auto" class="iconify iconify--mdi-light"><path d=""></path></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi-light']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
|
||||
it('Toggle inline and block using class', async () => {
|
||||
const iconData: IconifyIcon = {
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
};
|
||||
|
||||
const svg = await testIcon(
|
||||
'<span class="iconify" data-icon="mdi:home"></span>',
|
||||
iconData,
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" class="iconify iconify--mdi"><g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
|
||||
// Set inline by adding class
|
||||
svg.classList.add('iconify-inline');
|
||||
|
||||
return iconData;
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" class="iconify iconify--mdi iconify-inline" style="vertical-align: -0.125em;"><g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual(['vertical-align']);
|
||||
|
||||
// Set block by removing class
|
||||
svg.classList.remove('iconify-inline');
|
||||
|
||||
return iconData;
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" style="" class="iconify iconify--mdi"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
|
||||
it('Toggle inline and block using attributes', async () => {
|
||||
const iconData: IconifyIcon = {
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
};
|
||||
|
||||
const svg = await testIcon(
|
||||
'<span class="iconify" data-icon="mdi:home"></span>',
|
||||
iconData,
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" class="iconify iconify--mdi"><g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
|
||||
// Set inline by adding attribute
|
||||
svg.setAttribute('data-inline', 'data-inline');
|
||||
|
||||
return iconData;
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" data-inline="data-inline" class="iconify iconify--mdi" style="vertical-align: -0.125em;"><g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual(['vertical-align']);
|
||||
|
||||
// Set block by setting empty attribute
|
||||
svg.setAttribute('data-inline', '');
|
||||
|
||||
return iconData;
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" data-inline="" style="" class="iconify iconify--mdi"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
|
||||
it('Transformations', async () => {
|
||||
const svg = await testIcon(
|
||||
'<span class="iconify" data-icon="mdi:home" data-flip="horizontal"></span>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" data-flip="horizontal" class="iconify iconify--mdi"><g transform="translate(24 0) scale(-1 1)"><g></g></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
|
||||
// Rotate and flip
|
||||
svg.setAttribute('data-rotate', '90deg');
|
||||
svg.setAttribute('data-flip', 'vertical');
|
||||
|
||||
return {
|
||||
body: '<path d="" />',
|
||||
width: 32,
|
||||
height: 32,
|
||||
};
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" data-icon="mdi:home" data-flip="vertical" data-rotate="90deg" class="iconify iconify--mdi"><g transform="rotate(90 16 16) translate(0 32) scale(1 -1)"><path d=""></path></g></svg>',
|
||||
(svg) => {
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
|
||||
// Rotate and remove flip
|
||||
svg.setAttribute('data-rotate', '180deg');
|
||||
svg.removeAttribute('data-flip');
|
||||
|
||||
return {
|
||||
body: '<g />',
|
||||
};
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="mdi:home" data-rotate="180deg" class="iconify iconify--mdi"><g transform="rotate(180 8 8)"><g></g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
});
|
25
packages/iconify/tests/ready-test.ts
Normal file
25
packages/iconify/tests/ready-test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { cleanupGlobals, setupDOM } from './helpers';
|
||||
import { onReady } from '../src/helpers/ready';
|
||||
|
||||
describe('Testing onReady callback', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Testing onReady before DOM is loaded', (done) => {
|
||||
setupDOM('');
|
||||
expect(document.readyState).toBe('loading');
|
||||
onReady(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Testing onReady after DOM is loaded', (done) => {
|
||||
setupDOM('');
|
||||
expect(document.readyState).toBe('loading');
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
expect(document.readyState).toBe('interactive');
|
||||
onReady(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
147
packages/iconify/tests/render-node-test.ts
Normal file
147
packages/iconify/tests/render-node-test.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import { iconDefaults } from '@iconify/utils/lib/icon';
|
||||
import { cleanupGlobals, setupDOM, waitDOMReady } from './helpers';
|
||||
import { scanRootNode } from '../src/scanner/find';
|
||||
import { renderInlineSVG } from '../src/render/svg';
|
||||
import type { IconifyIcon } from '@iconify/utils/lib/icon';
|
||||
import { elementDataProperty, IconifyElement } from '../src/scanner/config';
|
||||
|
||||
describe('Testing rendering nodes', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
const testIcon = async (
|
||||
placeholder: string,
|
||||
data: IconifyIcon,
|
||||
expected: string
|
||||
): Promise<IconifyElement> => {
|
||||
setupDOM(placeholder);
|
||||
|
||||
await waitDOMReady();
|
||||
|
||||
// Find node
|
||||
const root = document.body;
|
||||
const items = scanRootNode(root);
|
||||
|
||||
expect(items.length).toBe(1);
|
||||
|
||||
// Get node and render it
|
||||
const { node, props } = items[0];
|
||||
const svg = renderInlineSVG(node, props, { ...iconDefaults, ...data });
|
||||
|
||||
// Find SVG in DOM
|
||||
expect(root.childNodes.length).toBe(1);
|
||||
const expectedSVG = root.childNodes[0];
|
||||
expect(expectedSVG).toBe(svg);
|
||||
|
||||
// Get HTML
|
||||
const html = root.innerHTML;
|
||||
expect(html).toBe(expected);
|
||||
|
||||
return svg;
|
||||
};
|
||||
|
||||
it('Rendering simple icon', async () => {
|
||||
const svg = await testIcon(
|
||||
'<span class="iconify" data-icon="mdi:home"></span>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" class="iconify iconify--mdi"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
|
||||
it('Inline icon and transformation', async () => {
|
||||
const svg = await testIcon(
|
||||
'<i class="iconify-inline" data-icon="mdi:home" data-flip="horizontal"></i>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" data-flip="horizontal" class="iconify-inline iconify iconify--mdi" style="vertical-align: -0.125em;"><g transform="translate(24 0) scale(-1 1)"><g></g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify', 'iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual(['vertical-align']);
|
||||
});
|
||||
|
||||
it('Passing attributes and style', async () => {
|
||||
const svg = await testIcon(
|
||||
'<span id="test" style="color: red; vertical-align: -0.1em;" class="iconify my-icon iconify--mdi" data-icon="mdi:home"></span>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" id="test" style="color: red; vertical-align: -0.1em;" data-icon="mdi:home" class="iconify my-icon iconify--mdi"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual([]); // All classes already existed on placeholder
|
||||
expect(data.addedStyles).toEqual([]); // Overwritten by entry in placeholder
|
||||
});
|
||||
|
||||
it('Inline icon and vertical-align', async () => {
|
||||
const svg = await testIcon(
|
||||
'<i class="iconify-inline" data-icon="mdi:home" style="vertical-align: 0"></i>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="mdi:home" style="vertical-align: 0" class="iconify-inline iconify iconify--mdi"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify', 'iconify--mdi']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
|
||||
it('Inline icon and custom style without ;', async () => {
|
||||
const svg = await testIcon(
|
||||
'<i class="iconify-inline" data-icon="@provider:mdi-light:home-outline" style="color: red"></i>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="@provider:mdi-light:home-outline" style="color: red; vertical-align: -0.125em;" class="iconify-inline iconify iconify--provider iconify--mdi-light"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual([
|
||||
'iconify',
|
||||
'iconify--provider',
|
||||
'iconify--mdi-light',
|
||||
]);
|
||||
expect(data.addedStyles).toEqual(['vertical-align']);
|
||||
});
|
||||
|
||||
it('Identical prefix and provider', async () => {
|
||||
const svg = await testIcon(
|
||||
'<i class="iconify" data-icon="@test:test:arrow-left"></i>',
|
||||
{
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" data-icon="@test:test:arrow-left" class="iconify iconify--test"><g></g></svg>'
|
||||
);
|
||||
|
||||
const data = svg[elementDataProperty];
|
||||
expect(data.status).toBe('loaded');
|
||||
expect(data.addedClasses).toEqual(['iconify--test']);
|
||||
expect(data.addedStyles).toEqual([]);
|
||||
});
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
import Iconify, { IconifyIconBuildResult } from '../';
|
||||
|
||||
describe('Testing Iconify render functions with Node.js', () => {
|
||||
const prefix = 'node-test-render';
|
||||
const name = prefix + ':icon';
|
||||
|
||||
it('Render functions', () => {
|
||||
// Add icon
|
||||
expect(
|
||||
Iconify.addIcon(name, {
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
// renderIcon() should work
|
||||
const expected: IconifyIconBuildResult = {
|
||||
attributes: {
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
preserveAspectRatio: 'xMidYMid meet',
|
||||
viewBox: '0 0 24 24',
|
||||
},
|
||||
body: '<g />',
|
||||
};
|
||||
expect(Iconify.renderIcon(name, {})).toEqual(expected);
|
||||
|
||||
// renderHTML() and renderSVG() should fail because document.createElement does not exist
|
||||
expect(Iconify.renderHTML(name, {})).toBe('');
|
||||
expect(Iconify.renderSVG(name, {})).toBeNull();
|
||||
});
|
||||
});
|
107
packages/iconify/tests/root-nodes-test.ts
Normal file
107
packages/iconify/tests/root-nodes-test.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { resetState, setupDOM, wrapHTML } from './helpers';
|
||||
import {
|
||||
listRootNodes,
|
||||
addBodyNode,
|
||||
addRootNode,
|
||||
removeRootNode,
|
||||
} from '../src/observer/root';
|
||||
|
||||
describe('Testing root nodes', () => {
|
||||
afterEach(resetState);
|
||||
|
||||
it('Testing body element', () => {
|
||||
setupDOM('');
|
||||
expect(document.readyState).toBe('loading');
|
||||
|
||||
// Add body node
|
||||
addBodyNode();
|
||||
|
||||
// List should have document.body
|
||||
expect(listRootNodes()).toEqual([
|
||||
{
|
||||
node: document.documentElement,
|
||||
temporary: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Adding and removing nodes', () => {
|
||||
setupDOM(
|
||||
wrapHTML('<div id="root-test"></div><div id="root-test2"></div>')
|
||||
);
|
||||
expect(document.readyState).toBe('loading');
|
||||
|
||||
// Get test nodes, make sure they exist
|
||||
const node1 = document.getElementById('root-test');
|
||||
expect(node1.tagName).toBe('DIV');
|
||||
expect(node1.getAttribute('id')).toBe('root-test');
|
||||
|
||||
const node2 = document.getElementById('root-test2');
|
||||
expect(node2.tagName).toBe('DIV');
|
||||
expect(node2.getAttribute('id')).toBe('root-test2');
|
||||
|
||||
// Add body node and temporary nodes
|
||||
addBodyNode();
|
||||
addRootNode(node1);
|
||||
addRootNode(node2, true);
|
||||
|
||||
// List nodes
|
||||
expect(listRootNodes()).toEqual([
|
||||
{
|
||||
node: document.documentElement,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node1,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node2,
|
||||
temporary: true,
|
||||
},
|
||||
]);
|
||||
|
||||
// Switch type for node2
|
||||
addRootNode(node2);
|
||||
expect(listRootNodes()).toEqual([
|
||||
{
|
||||
node: document.documentElement,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node1,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node2,
|
||||
temporary: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// Remove node2
|
||||
removeRootNode(node2);
|
||||
expect(listRootNodes()).toEqual([
|
||||
{
|
||||
node: document.documentElement,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node1,
|
||||
temporary: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// Add duplicate node1
|
||||
addRootNode(node1, true);
|
||||
expect(listRootNodes()).toEqual([
|
||||
{
|
||||
node: document.documentElement,
|
||||
temporary: false,
|
||||
},
|
||||
{
|
||||
node: node1,
|
||||
temporary: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
175
packages/iconify/tests/scan-dom-test.ts
Normal file
175
packages/iconify/tests/scan-dom-test.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { iconExists, addIcon } from '@iconify/core/lib/storage/functions';
|
||||
import {
|
||||
fakeAPI,
|
||||
nextPrefix,
|
||||
setupDOM,
|
||||
waitDOMReady,
|
||||
resetState,
|
||||
mockAPIData,
|
||||
awaitUntil,
|
||||
} from './helpers';
|
||||
import { addBodyNode } from '../src/observer/root';
|
||||
import { scanDOM } from '../src/scanner/index';
|
||||
import { elementDataProperty } from '../src/scanner/config';
|
||||
|
||||
describe('Scanning DOM', () => {
|
||||
const provider = nextPrefix();
|
||||
|
||||
beforeAll(() => {
|
||||
fakeAPI(provider);
|
||||
});
|
||||
|
||||
afterEach(resetState);
|
||||
|
||||
it('Rendering preloaded icon', async () => {
|
||||
const prefix = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix}:home`;
|
||||
|
||||
// Add icon
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
addIcon(iconName, {
|
||||
body: '<g />',
|
||||
});
|
||||
expect(iconExists(iconName)).toBe(true);
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
const placeholder = document.body.childNodes[0];
|
||||
expect(placeholder[elementDataProperty]).toBeUndefined();
|
||||
|
||||
// Scan DOM
|
||||
scanDOM();
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" class="iconify iconify--${provider} iconify--${prefix}"><g></g></svg>`
|
||||
);
|
||||
const svg = document.body.childNodes[0];
|
||||
const svgData = svg[elementDataProperty];
|
||||
expect(svgData.status).toBe('loaded');
|
||||
expect(svgData.name).toEqual(iconName);
|
||||
});
|
||||
|
||||
it('Loading icon', async () => {
|
||||
const prefix = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix}:home`;
|
||||
|
||||
// Add icon with API
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {
|
||||
home: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
|
||||
// Check HTML and data
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
const placeholder = document.body.childNodes[0];
|
||||
expect(placeholder[elementDataProperty]).toBeUndefined();
|
||||
|
||||
// Scan DOM
|
||||
scanDOM();
|
||||
|
||||
// Check HTML again
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
expect(placeholder).toBe(document.body.childNodes[0]);
|
||||
const placeholderData = placeholder[elementDataProperty];
|
||||
expect(placeholderData.status).toBe('loading');
|
||||
expect(placeholderData.name).toEqual(iconName);
|
||||
expect(placeholderData.isSVG).toBeFalsy();
|
||||
|
||||
// Wait for re-render
|
||||
await awaitUntil(() => document.body.childNodes[0] !== placeholder);
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" data-icon="${iconName}" class="iconify iconify--${provider} iconify--${prefix}"><g></g></svg>`
|
||||
);
|
||||
const svg = document.body.childNodes[0];
|
||||
const svgData = svg[elementDataProperty];
|
||||
expect(svgData.status).toBe('loaded');
|
||||
expect(svgData.isSVG).toBe(true);
|
||||
expect(svgData.name).toEqual(iconName);
|
||||
});
|
||||
|
||||
it('Missing icon', async () => {
|
||||
const prefix = nextPrefix();
|
||||
const iconName = `@${provider}:${prefix}:home`;
|
||||
|
||||
// Add icon with API
|
||||
expect(iconExists(iconName)).toBe(false);
|
||||
mockAPIData({
|
||||
type: 'icons',
|
||||
provider,
|
||||
prefix,
|
||||
response: {
|
||||
prefix,
|
||||
icons: {},
|
||||
not_found: ['home'],
|
||||
},
|
||||
});
|
||||
|
||||
// Setup DOM and wait for it to be ready
|
||||
setupDOM(`<span class="iconify" data-icon="${iconName}"></span>`);
|
||||
await waitDOMReady();
|
||||
|
||||
// Observe body
|
||||
addBodyNode();
|
||||
|
||||
// Check HTML and data
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
const placeholder = document.body.childNodes[0];
|
||||
expect(placeholder[elementDataProperty]).toBeUndefined();
|
||||
|
||||
// Scan DOM
|
||||
scanDOM();
|
||||
|
||||
// Check HTML again
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
expect(placeholder).toBe(document.body.childNodes[0]);
|
||||
const placeholderData = placeholder[elementDataProperty];
|
||||
expect(placeholderData.status).toBe('loading');
|
||||
expect(placeholderData.name).toEqual(iconName);
|
||||
expect(placeholderData.isSVG).toBeFalsy();
|
||||
|
||||
// Wait for re-render
|
||||
await awaitUntil(() => placeholderData.status === 'missing');
|
||||
|
||||
// Check HTML
|
||||
expect(document.body.innerHTML).toBe(
|
||||
`<span class="iconify" data-icon="${iconName}"></span>`
|
||||
);
|
||||
});
|
||||
});
|
62
packages/iconify/tests/scan-node-test.ts
Normal file
62
packages/iconify/tests/scan-node-test.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { cleanupGlobals, setupDOM, waitDOMReady } from './helpers';
|
||||
import { scanRootNode } from '../src/scanner/find';
|
||||
import { getElementProps } from '../src/scanner/get-props';
|
||||
|
||||
describe('Testing scanning nodes', () => {
|
||||
afterEach(cleanupGlobals);
|
||||
|
||||
it('Finding basic placeholders', async () => {
|
||||
setupDOM(`
|
||||
<span class="iconify" data-icon="mdi:home"></span>
|
||||
<span class="iconify-inline" data-icon="mdi-light:home"></span>
|
||||
<p>
|
||||
<i class="iconify" data-icon="mdi:home-outline"></i>
|
||||
<i class="iconify-inline" data-icon="ic:baseline-home"></i>
|
||||
</p>
|
||||
`);
|
||||
|
||||
await waitDOMReady();
|
||||
|
||||
const root = document.documentElement;
|
||||
const items = scanRootNode(root);
|
||||
|
||||
// 4 nodes
|
||||
expect(items.length).toBe(4);
|
||||
|
||||
// span.iconify
|
||||
const node0 = root.querySelector('span.iconify');
|
||||
expect(items[0].node).toEqual(node0);
|
||||
expect(items[0].props).toEqual(getElementProps(node0));
|
||||
|
||||
// span.iconify-inline
|
||||
const node1 = root.querySelector('span.iconify-inline');
|
||||
expect(items[1].node).toEqual(node1);
|
||||
expect(items[1].props).toEqual(getElementProps(node1));
|
||||
|
||||
// i.iconify
|
||||
const node2 = root.querySelector('i.iconify');
|
||||
expect(items[2].node).toEqual(node2);
|
||||
expect(items[2].props).toEqual(getElementProps(node2));
|
||||
|
||||
// i.iconify-inline
|
||||
const node3 = root.querySelector('i.iconify-inline');
|
||||
expect(items[3].node).toEqual(node3);
|
||||
expect(items[3].props).toEqual(getElementProps(node3));
|
||||
});
|
||||
|
||||
it('Invalid placeholders', async () => {
|
||||
setupDOM(`
|
||||
<span class="iconify" data-icon="badicon"></span>
|
||||
<span data-icon="prefix:missing-class"></span>
|
||||
<strong class="iconify" data-icon="prefix:invalid-tag"></strong>
|
||||
<svg class="iconify iconify--prefix" data-icon="prefix:svg-without-data"></svg>
|
||||
`);
|
||||
|
||||
await waitDOMReady();
|
||||
|
||||
const root = document.documentElement;
|
||||
const items = scanRootNode(root);
|
||||
|
||||
expect(items.length).toBe(0);
|
||||
});
|
||||
});
|
@ -1,75 +0,0 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname } from 'path';
|
||||
import Iconify, { IconifyIcon } from '../';
|
||||
|
||||
describe('Testing Iconify with Node.js', () => {
|
||||
it('Basic functions', () => {
|
||||
expect(typeof Iconify).toBe('object');
|
||||
|
||||
// Placeholder value should have been replaced during compilation
|
||||
const version = JSON.parse(
|
||||
readFileSync(dirname(__dirname) + '/package.json', 'utf8')
|
||||
).version;
|
||||
expect(Iconify.getVersion()).toBe(version);
|
||||
});
|
||||
|
||||
it('Builder functions', () => {
|
||||
// calculateSize() should work in Node.js
|
||||
expect(Iconify.calculateSize('24px', 2)).toBe('48px');
|
||||
|
||||
// replaceIDs() should work in Node.js
|
||||
const test = '<div id="foo" />';
|
||||
expect(Iconify.replaceIDs(test)).not.toBe(test);
|
||||
});
|
||||
|
||||
it('Storage functions', () => {
|
||||
const prefix = 'node-test-storage';
|
||||
const name = prefix + ':bar';
|
||||
|
||||
// Empty results
|
||||
expect(Iconify.iconExists(name)).toBe(false);
|
||||
expect(Iconify.getIcon(name)).toBeNull();
|
||||
expect(Iconify.listIcons('', prefix)).toEqual([]);
|
||||
|
||||
// Test addIcon()
|
||||
expect(
|
||||
Iconify.addIcon(name, {
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
expect(Iconify.iconExists(name)).toBe(true);
|
||||
const expected: Required<IconifyIcon> = {
|
||||
body: '<g />',
|
||||
width: 24,
|
||||
height: 24,
|
||||
left: 0,
|
||||
top: 0,
|
||||
hFlip: false,
|
||||
vFlip: false,
|
||||
rotate: 0,
|
||||
};
|
||||
expect(Iconify.getIcon(name)).toEqual(expected);
|
||||
expect(Iconify.listIcons('', prefix)).toEqual([name]);
|
||||
|
||||
// Test addCollection()
|
||||
expect(
|
||||
Iconify.addCollection({
|
||||
prefix,
|
||||
icons: {
|
||||
test1: {
|
||||
body: '<g />',
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(Iconify.listIcons('', prefix)).toEqual([
|
||||
name,
|
||||
prefix + ':test1',
|
||||
]);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user