2
0
mirror of https://github.com/iconify/iconify.git synced 2025-01-22 14:48:24 +00:00

Implement icon providers (similar to namespaces)

This commit is contained in:
Vjacheslav Trushkin 2020-05-29 22:08:45 +03:00
parent 0956dc7f9a
commit 2aad697264
31 changed files with 1204 additions and 535 deletions

View File

@ -2,14 +2,14 @@ import mocha from 'mocha';
import chai from 'chai'; import chai from 'chai';
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api'; import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
import { API } from '@iconify/core/lib/api/'; import { API } from '@iconify/core/lib/api/';
import { setAPIModule } from '@iconify/core/lib/api/modules'; import { setDefaultAPIModule } from '@iconify/core/lib/api/modules';
import { setAPIConfig } from '@iconify/core/lib/api/config'; import { setAPIConfig } from '@iconify/core/lib/api/config';
import { coreModules } from '@iconify/core/lib/modules'; import { coreModules } from '@iconify/core/lib/modules';
const expect = chai.expect; const expect = chai.expect;
// Set API // Set API
setAPIModule({ setDefaultAPIModule({
prepare: prepareQuery, prepare: prepareQuery,
send: sendQuery, send: sendQuery,
}); });
@ -21,7 +21,8 @@ function nextPrefix(): string {
} }
describe('Testing fake API', () => { describe('Testing fake API', () => {
it('Loading results', done => { it('Loading results', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
const data: FakeData = { const data: FakeData = {
icons: ['icon1', 'icon2'], icons: ['icon1', 'icon2'],
@ -41,24 +42,26 @@ describe('Testing fake API', () => {
height: 24, height: 24,
}, },
}; };
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
}, setFakeData(provider, prefix, data);
prefix
);
setFakeData(prefix, data);
// Attempt to load icons // Attempt to load icons
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending) => { (loaded, missing, pending) => {
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -68,7 +71,8 @@ describe('Testing fake API', () => {
); );
}); });
it('Loading results with delay', done => { it('Loading results with delay', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
const data: FakeData = { const data: FakeData = {
icons: ['icon1', 'icon2'], icons: ['icon1', 'icon2'],
@ -89,23 +93,22 @@ describe('Testing fake API', () => {
height: 24, height: 24,
}, },
}; };
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
}, setFakeData(provider, prefix, data);
prefix
);
setFakeData(prefix, data);
// Attempt to load icons // Attempt to load icons
const start = Date.now(); const start = Date.now();
API.loadIcons( API.loadIcons(
[ [
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -113,10 +116,12 @@ describe('Testing fake API', () => {
(loaded, missing, pending) => { (loaded, missing, pending) => {
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -129,7 +134,8 @@ describe('Testing fake API', () => {
); );
}); });
it('Loading partial results', done => { it('Loading partial results', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
const data: FakeData = { const data: FakeData = {
icons: ['icon1'], icons: ['icon1'],
@ -146,21 +152,21 @@ describe('Testing fake API', () => {
height: 24, height: 24,
}, },
}; };
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], rotate: 20,
rotate: 20, timeout: 100,
timeout: 100, limit: 1,
limit: 1, });
}, setFakeData(provider, prefix, data);
prefix
);
setFakeData(prefix, data);
// Attempt to load icons // Attempt to load icons
let counter = 0; let counter = 0;
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending) => { (loaded, missing, pending) => {
try { try {
counter++; counter++;
@ -169,12 +175,14 @@ describe('Testing fake API', () => {
// Loaded icon1 // Loaded icon1
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
]); ]);
expect(pending).to.be.eql([ expect(pending).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },

View File

@ -46,6 +46,7 @@ describe('Testing legacy finder', () => {
// Test all icons // Test all icons
testIcon( testIcon(
{ {
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}, },
@ -54,6 +55,7 @@ describe('Testing legacy finder', () => {
testIcon( testIcon(
{ {
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}, },
@ -62,6 +64,7 @@ describe('Testing legacy finder', () => {
testIcon( testIcon(
{ {
provider: '',
prefix: 'ic', prefix: 'ic',
name: 'baseline-account', name: 'baseline-account',
}, },

View File

@ -46,6 +46,7 @@ describe('Testing finder', () => {
// Test all icons // Test all icons
testIcon( testIcon(
{ {
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}, },
@ -54,6 +55,7 @@ describe('Testing finder', () => {
testIcon( testIcon(
{ {
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}, },
@ -62,6 +64,7 @@ describe('Testing finder', () => {
testIcon( testIcon(
{ {
provider: '',
prefix: 'ic', prefix: 'ic',
name: 'baseline-account', name: 'baseline-account',
}, },

View File

@ -18,7 +18,7 @@ addFinder(iconifyFinder);
describe('Testing legacy renderer', () => { describe('Testing legacy renderer', () => {
// Add mentioned icons to storage // Add mentioned icons to storage
const storage = getStorage('mdi'); const storage = getStorage('', 'mdi');
addIconSet(storage, { addIconSet(storage, {
prefix: 'mdi', prefix: 'mdi',
icons: { icons: {
@ -82,6 +82,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account-cash', name: 'account-cash',
}); });
@ -124,6 +125,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -167,6 +169,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account-box', name: 'account-box',
}); });
@ -214,6 +217,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -255,6 +259,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -299,6 +304,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -372,6 +378,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -537,6 +544,7 @@ describe('Testing legacy renderer', () => {
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
expect(element.name).to.not.be.eql(lastElement.name); expect(element.name).to.not.be.eql(lastElement.name);
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -582,6 +590,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -630,6 +639,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -676,6 +686,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -764,6 +775,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -861,6 +873,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -946,6 +959,7 @@ describe('Testing legacy renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });

View File

@ -18,7 +18,7 @@ addFinder(iconifyFinder);
describe('Testing renderer', () => { describe('Testing renderer', () => {
// Add mentioned icons to storage // Add mentioned icons to storage
const storage = getStorage('mdi'); const storage = getStorage('', 'mdi');
addIconSet(storage, { addIconSet(storage, {
prefix: 'mdi', prefix: 'mdi',
icons: { icons: {
@ -82,6 +82,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account-cash', name: 'account-cash',
}); });
@ -124,6 +125,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -167,6 +169,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account-box', name: 'account-box',
}); });
@ -214,6 +217,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -255,6 +259,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -301,6 +306,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -374,6 +380,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -541,6 +548,7 @@ describe('Testing renderer', () => {
expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties expect(element).to.not.be.eql(lastElement); // different 'element' and 'name' properties
expect(element.name).to.not.be.eql(lastElement.name); expect(element.name).to.not.be.eql(lastElement.name);
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'account', name: 'account',
}); });
@ -586,6 +594,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -634,6 +643,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -680,6 +690,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -768,6 +779,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -865,6 +877,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });
@ -950,6 +963,7 @@ describe('Testing renderer', () => {
// Test element // Test element
expect(element.name).to.be.eql({ expect(element.name).to.be.eql({
provider: '',
prefix: 'mdi', prefix: 'mdi',
name: 'home', name: 'home',
}); });

View File

@ -17,7 +17,7 @@ addFinder(iconifyIconFinder);
describe('Scanning DOM', () => { describe('Scanning DOM', () => {
// Add mentioned icons to storage // Add mentioned icons to storage
const storage = getStorage('mdi'); const storage = getStorage('', 'mdi');
addIconSet(storage, { addIconSet(storage, {
prefix: 'mdi', prefix: 'mdi',
icons: { icons: {

View File

@ -5,7 +5,7 @@ import { getNode } from './node';
import { addFinder } from '@iconify/iconify/lib/finder'; import { addFinder } from '@iconify/iconify/lib/finder';
import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api'; import { FakeData, setFakeData, prepareQuery, sendQuery } from './fake-api';
import { API } from '@iconify/core/lib/api/'; import { API } from '@iconify/core/lib/api/';
import { setAPIModule } from '@iconify/core/lib/api/modules'; import { setDefaultAPIModule } from '@iconify/core/lib/api/modules';
import { setAPIConfig } from '@iconify/core/lib/api/config'; import { setAPIConfig } from '@iconify/core/lib/api/config';
import { coreModules } from '@iconify/core/lib/modules'; import { coreModules } from '@iconify/core/lib/modules';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify'; import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
@ -20,7 +20,7 @@ addFinder(iconifyFinder);
addFinder(iconifyIconFinder); addFinder(iconifyIconFinder);
// Set API // Set API
setAPIModule({ setDefaultAPIModule({
prepare: prepareQuery, prepare: prepareQuery,
send: sendQuery, send: sendQuery,
}); });
@ -33,16 +33,14 @@ function nextPrefix(): string {
describe('Scanning DOM with API', () => { describe('Scanning DOM with API', () => {
it('Scan DOM with API', (done) => { it('Scan DOM with API', (done) => {
const provider = nextPrefix();
const prefix1 = nextPrefix(); const prefix1 = nextPrefix();
const prefix2 = nextPrefix(); const prefix2 = nextPrefix();
// Set fake API hosts to make test reliable // Set fake API hosts to make test reliable
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
},
[prefix1, prefix2]
);
// Set icons, load them with various delay // Set icons, load them with various delay
const data1: FakeData = { const data1: FakeData = {
@ -64,7 +62,7 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix1, data1); setFakeData(provider, prefix1, data1);
const data2: FakeData = { const data2: FakeData = {
icons: ['account', 'account-box'], icons: ['account', 'account-box'],
@ -85,24 +83,32 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix2, data2); setFakeData(provider, prefix2, data2);
const node = getNode('scan-dom'); const node = getNode('scan-dom');
node.innerHTML = node.innerHTML =
'<div><p>Testing scanning DOM with API</p><ul>' + '<div><p>Testing scanning DOM with API</p><ul>' +
'<li>Inline icons:' + '<li>Inline icons:' +
' <span class="iconify iconify-inline" data-icon="' + ' <span class="iconify iconify-inline" data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' + ':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="' + ' <i class="iconify-inline test-icon iconify--mdi-account" data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' + ':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
'</li>' + '</li>' +
'<li>Block icons:' + '<li>Block icons:' +
' <iconify-icon data-icon="' + ' <iconify-icon data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' + ':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' +
' <i class="iconify-icon" data-icon="' + ' <i class="iconify-icon" data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account-box" data-inline="true" data-rotate="2" data-width="42"></i>' + ':account-box" data-inline="true" data-rotate="2" data-width="42"></i>' +
'</li>' + '</li>' +
@ -133,16 +139,14 @@ describe('Scanning DOM with API', () => {
}); });
it('Changing icon name before it loaded', (done) => { it('Changing icon name before it loaded', (done) => {
const provider = nextPrefix();
const prefix1 = nextPrefix(); const prefix1 = nextPrefix();
const prefix2 = nextPrefix(); const prefix2 = nextPrefix();
// Set fake API hosts to make test reliable // Set fake API hosts to make test reliable
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
},
[prefix1, prefix2]
);
// Set icons, load them with various delay // Set icons, load them with various delay
const data1: FakeData = { const data1: FakeData = {
@ -164,7 +168,7 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix1, data1); setFakeData(provider, prefix1, data1);
const data2: FakeData = { const data2: FakeData = {
icons: ['account', 'account-box'], icons: ['account', 'account-box'],
@ -185,7 +189,7 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix2, data2); setFakeData(provider, prefix2, data2);
const data1b: FakeData = { const data1b: FakeData = {
icons: ['account', 'account-box'], icons: ['account', 'account-box'],
@ -206,24 +210,32 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix1, data1b); setFakeData(provider, prefix1, data1b);
const node = getNode('scan-dom'); const node = getNode('scan-dom');
node.innerHTML = node.innerHTML =
'<div><p>Testing scanning DOM with API: renamed icon</p><ul>' + '<div><p>Testing scanning DOM with API: renamed icon</p><ul>' +
'<li>Default finder:' + '<li>Default finder:' +
' <span class="iconify-inline first-icon" data-icon="' + ' <span class="iconify-inline first-icon" data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' + ':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
' <i class="iconify-inline second-icon iconify--mdi-account" data-icon="' + ' <i class="iconify-inline second-icon iconify--mdi-account" data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' + ':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
'</li>' + '</li>' +
'<li>IconifyIcon finder:' + '<li>IconifyIcon finder:' +
' <iconify-icon class="third-icon" data-icon="' + ' <iconify-icon class="third-icon" data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' + ':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' +
' <iconify-icon class="fourth-icon" data-icon="' + ' <iconify-icon class="fourth-icon" data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' + ':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
'</li>' + '</li>' +
@ -244,7 +256,10 @@ describe('Scanning DOM with API', () => {
const icon = node.querySelector('iconify-icon[title]'); const icon = node.querySelector('iconify-icon[title]');
expect(icon).to.not.be.equal(null); expect(icon).to.not.be.equal(null);
expect(icon.getAttribute('class')).to.be.equal('third-icon'); expect(icon.getAttribute('class')).to.be.equal('third-icon');
icon.setAttribute('data-icon', prefix1 + ':account'); icon.setAttribute(
'data-icon',
'@' + provider + ':' + prefix1 + ':account'
);
// First API response should have loaded, but only 1 icon should have been rendered // First API response should have loaded, but only 1 icon should have been rendered
setTimeout(() => { setTimeout(() => {
@ -273,16 +288,14 @@ describe('Scanning DOM with API', () => {
}); });
it('Changing icon name before it loaded to invalid name', (done) => { it('Changing icon name before it loaded to invalid name', (done) => {
const provider = nextPrefix();
const prefix1 = nextPrefix(); const prefix1 = nextPrefix();
const prefix2 = nextPrefix(); const prefix2 = nextPrefix();
// Set fake API hosts to make test reliable // Set fake API hosts to make test reliable
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
},
[prefix1, prefix2]
);
// Set icons, load them with various delay // Set icons, load them with various delay
const data1: FakeData = { const data1: FakeData = {
@ -304,7 +317,7 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix1, data1); setFakeData(provider, prefix1, data1);
const data2: FakeData = { const data2: FakeData = {
icons: ['account', 'account-box'], icons: ['account', 'account-box'],
@ -325,24 +338,32 @@ describe('Scanning DOM with API', () => {
height: 24, height: 24,
}, },
}; };
setFakeData(prefix2, data2); setFakeData(provider, prefix2, data2);
const node = getNode('scan-dom'); const node = getNode('scan-dom');
node.innerHTML = node.innerHTML =
'<div><p>Testing scanning DOM with API: invalid name</p><ul>' + '<div><p>Testing scanning DOM with API: invalid name</p><ul>' +
'<li>Inline icons:' + '<li>Inline icons:' +
' <span class="iconify" data-icon="' + ' <span class="iconify" data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':home" style="color: red; box-shadow: 0 0 2px black;"></span>' + ':home" style="color: red; box-shadow: 0 0 2px black;"></span>' +
' <i class="iconify test-icon iconify--mdi-account" data-icon="' + ' <i class="iconify test-icon iconify--mdi-account" data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' + ':account" style="vertical-align: 0;" data-flip="horizontal" aria-hidden="false"></i>' +
'</li>' + '</li>' +
'<li>Block icons:' + '<li>Block icons:' +
' <iconify-icon data-icon="' + ' <iconify-icon data-icon="@' +
provider +
':' +
prefix1 + prefix1 +
':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' + ':account-cash" title="&lt;Cash&gt;!"></iconify-icon>' +
' <iconify-icon data-icon="' + ' <iconify-icon data-icon="@' +
provider +
':' +
prefix2 + prefix2 +
':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' + ':account-box" data-inline="true" data-rotate="2" data-width="42"></iconify-icon>' +
'</li>' + '</li>' +
@ -355,7 +376,7 @@ describe('Scanning DOM with API', () => {
// Change icon name // Change icon name
const icon = node.querySelector('iconify-icon[title]'); const icon = node.querySelector('iconify-icon[title]');
expect(icon).to.not.be.equal(null); expect(icon).to.not.be.equal(null);
icon.setAttribute('data-icon', 'foo'); icon.setAttribute('data-icon', '@' + provider + ':foo');
// First API response should have loaded, but only 1 icon // First API response should have loaded, but only 1 icon
setTimeout(() => { setTimeout(() => {

View File

@ -19,13 +19,23 @@ export interface FakeData {
/** /**
* Fake data storage * Fake data storage
*/ */
const fakeData: Record<string, FakeData[]> = Object.create(null); const fakeData: Record<string, Record<string, FakeData[]>> = Object.create(
null
);
export function setFakeData(prefix: string, item: FakeData): void { export function setFakeData(
if (fakeData[prefix] === void 0) { provider: string,
fakeData[prefix] = []; prefix: string,
item: FakeData
): void {
if (fakeData[provider] === void 0) {
fakeData[provider] = Object.create(null);
} }
fakeData[prefix].push(item); const providerFakeData = fakeData[provider];
if (providerFakeData[prefix] === void 0) {
providerFakeData[prefix] = [];
}
providerFakeData[prefix].push(item);
} }
interface FakeAPIQueryParams extends APIQueryParams { interface FakeAPIQueryParams extends APIQueryParams {
@ -36,6 +46,7 @@ interface FakeAPIQueryParams extends APIQueryParams {
* Prepare params * Prepare params
*/ */
export const prepareQuery: IconifyAPIPrepareQuery = ( export const prepareQuery: IconifyAPIPrepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
@ -43,10 +54,15 @@ export const prepareQuery: IconifyAPIPrepareQuery = (
const items: APIQueryParams[] = []; const items: APIQueryParams[] = [];
let missing = icons.slice(0); let missing = icons.slice(0);
if (fakeData[prefix] !== void 0) { if (fakeData[provider] === void 0) {
fakeData[prefix].forEach(item => { fakeData[provider] = Object.create(null);
}
const providerFakeData = fakeData[provider];
if (providerFakeData[prefix] !== void 0) {
providerFakeData[prefix].forEach((item) => {
const matches = item.icons.filter( const matches = item.icons.filter(
icon => missing.indexOf(icon) !== -1 (icon) => missing.indexOf(icon) !== -1
); );
if (!matches.length) { if (!matches.length) {
// No match // No match
@ -54,8 +70,9 @@ export const prepareQuery: IconifyAPIPrepareQuery = (
} }
// Contains at least one matching icon // Contains at least one matching icon
missing = missing.filter(icon => matches.indexOf(icon) === -1); missing = missing.filter((icon) => matches.indexOf(icon) === -1);
const query: FakeAPIQueryParams = { const query: FakeAPIQueryParams = {
provider,
prefix, prefix,
icons: matches, icons: matches,
data: item, data: item,
@ -75,6 +92,7 @@ export const sendQuery: IconifyAPISendQuery = (
params: APIQueryParams, params: APIQueryParams,
status: RedundancyPendingItem status: RedundancyPendingItem
): void => { ): void => {
const provider = params.provider;
const prefix = params.prefix; const prefix = params.prefix;
const icons = params.icons; const icons = params.icons;
@ -88,7 +106,13 @@ export const sendQuery: IconifyAPISendQuery = (
} }
const sendResponse = () => { const sendResponse = () => {
console.log('Sending data for prefix "' + prefix + '", icons:', icons); console.log(
'Sending data for prefix "' +
(provider === '' ? '' : '@' + provider + ':') +
prefix +
'", icons:',
icons
);
status.done(data.data); status.done(data.data);
}; };

View File

@ -4,6 +4,7 @@ import {
} from '../interfaces/loader'; } from '../interfaces/loader';
import { getStorage } from '../storage'; import { getStorage } from '../storage';
import { SortedIcons } from '../icon/sort'; import { SortedIcons } from '../icon/sort';
import { IconifyIconSource } from '../icon/name';
/** /**
* Storage for callbacks * Storage for callbacks
@ -22,49 +23,70 @@ interface CallbackItem {
abort: IconifyIconLoaderAbort; abort: IconifyIconLoaderAbort;
} }
// Records sorted by provider and prefix
// This export is only for unit testing, should not be used // This export is only for unit testing, should not be used
export const callbacks: Record<string, CallbackItem[]> = Object.create(null); export const callbacks: Record<
const pendingUpdates: Record<string, boolean> = Object.create(null); string,
Record<string, CallbackItem[]>
> = Object.create(null);
const pendingUpdates: Record<string, Record<string, boolean>> = Object.create(
null
);
/** /**
* Remove callback * Remove callback
*/ */
function removeCallback(prefixes: string[], id: number): void { function removeCallback(sources: IconifyIconSource[], id: number): void {
prefixes.forEach(prefix => { sources.forEach((source) => {
const items = callbacks[prefix]; const provider = source.provider;
if (callbacks[provider] === void 0) {
return;
}
const providerCallbacks = callbacks[provider];
const prefix = source.prefix;
const items = providerCallbacks[prefix];
if (items) { if (items) {
callbacks[prefix] = items.filter(row => row.id !== id); providerCallbacks[prefix] = items.filter((row) => row.id !== id);
} }
}); });
} }
/** /**
* Update all callbacks for prefix * Update all callbacks for provider and prefix
*/ */
export function updateCallbacks(prefix: string): void { export function updateCallbacks(provider: string, prefix: string): void {
if (!pendingUpdates[prefix]) { if (pendingUpdates[provider] === void 0) {
pendingUpdates[prefix] = true; pendingUpdates[provider] = Object.create(null);
setTimeout(() => { }
pendingUpdates[prefix] = false; const providerPendingUpdates = pendingUpdates[provider];
if (callbacks[prefix] === void 0) { if (!providerPendingUpdates[prefix]) {
providerPendingUpdates[prefix] = true;
setTimeout(() => {
providerPendingUpdates[prefix] = false;
if (
callbacks[provider] === void 0 ||
callbacks[provider][prefix] === void 0
) {
return; return;
} }
// Get all items // Get all items
const items = callbacks[prefix].slice(0); const items = callbacks[provider][prefix].slice(0);
if (!items.length) { if (!items.length) {
return; return;
} }
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
// Check each item for changes // Check each item for changes
let hasPending = false; let hasPending = false;
items.forEach((item: CallbackItem) => { items.forEach((item: CallbackItem) => {
const icons = item.icons; const icons = item.icons;
const oldLength = icons.pending.length; const oldLength = icons.pending.length;
icons.pending = icons.pending.filter(icon => { icons.pending = icons.pending.filter((icon) => {
if (icon.prefix !== prefix) { if (icon.prefix !== prefix) {
// Checking only current prefix // Checking only current prefix
return true; return true;
@ -74,12 +96,14 @@ export function updateCallbacks(prefix: string): void {
if (storage.icons[name] !== void 0) { if (storage.icons[name] !== void 0) {
// Loaded // Loaded
icons.loaded.push({ icons.loaded.push({
provider,
prefix, prefix,
name, name,
}); });
} else if (storage.missing[name] !== void 0) { } else if (storage.missing[name] !== void 0) {
// Missing // Missing
icons.missing.push({ icons.missing.push({
provider,
prefix, prefix,
name, name,
}); });
@ -96,7 +120,15 @@ export function updateCallbacks(prefix: string): void {
if (icons.pending.length !== oldLength) { if (icons.pending.length !== oldLength) {
if (!hasPending) { if (!hasPending) {
// All icons have been loaded - remove callback from prefix // All icons have been loaded - remove callback from prefix
removeCallback([prefix], item.id); removeCallback(
[
{
provider,
prefix,
},
],
item.id
);
} }
item.callback( item.callback(
icons.loaded.slice(0), icons.loaded.slice(0),
@ -121,11 +153,11 @@ let idCounter = 0;
export function storeCallback( export function storeCallback(
callback: IconifyIconLoaderCallback, callback: IconifyIconLoaderCallback,
icons: SortedIcons, icons: SortedIcons,
pendingPrefixes: string[] pendingSources: IconifyIconSource[]
): IconifyIconLoaderAbort { ): IconifyIconLoaderAbort {
// Create unique id and abort function // Create unique id and abort function
const id = idCounter++; const id = idCounter++;
const abort = removeCallback.bind(null, pendingPrefixes, id); const abort = removeCallback.bind(null, pendingSources, id);
if (!icons.pending.length) { if (!icons.pending.length) {
// Do not store item without pending icons and return function that does nothing // Do not store item without pending icons and return function that does nothing
@ -140,11 +172,17 @@ export function storeCallback(
abort: abort, abort: abort,
}; };
pendingPrefixes.forEach(prefix => { pendingSources.forEach((source) => {
if (callbacks[prefix] === void 0) { const provider = source.provider;
callbacks[prefix] = []; const prefix = source.prefix;
if (callbacks[provider] === void 0) {
callbacks[provider] = Object.create(null);
} }
callbacks[prefix].push(item); const providerCallbacks = callbacks[provider];
if (providerCallbacks[prefix] === void 0) {
providerCallbacks[prefix] = [];
}
providerCallbacks[prefix].push(item);
}); });
return abort; return abort;

View File

@ -1,5 +1,4 @@
import { RedundancyConfig } from '@cyberalien/redundancy'; import { RedundancyConfig } from '@cyberalien/redundancy';
import { merge } from '../misc/merge';
/** /**
* API config * API config
@ -12,17 +11,52 @@ export interface IconifyAPIConfig extends RedundancyConfig {
maxURL: number; maxURL: number;
} }
/** export type PartialIconifyAPIConfig = Partial<IconifyAPIConfig>;
* Local storage interfaces
*/
interface ConfigStorage {
// API configuration for all prefixes
default: IconifyAPIConfig;
// Prefix specific API configuration /**
prefixes: Record<string, IconifyAPIConfig>; * Create full API configuration from partial data
*/
function createConfig(
source: PartialIconifyAPIConfig
): IconifyAPIConfig | null {
if (!source.resources) {
return null;
}
const result: IconifyAPIConfig = {
// API hosts
resources: source.resources,
// Root path
path: source.path === void 0 ? '/' : source.path,
// URL length limit
maxURL: source.maxURL ? source.maxURL : 500,
// Timeout before next host is used.
rotate: source.rotate ? source.rotate : 750,
// Timeout to retry same host.
timeout: source.timeout ? source.timeout : 5000,
// Number of attempts for each host.
limit: source.limit ? source.limit : 2,
// Randomise default API end point.
random: source.random === true,
// Start index
index: source.index ? source.index : 0,
};
return result;
} }
/**
* Local storage
*/
const configStorage: Record<string, IconifyAPIConfig> = Object.create(null);
/** /**
* Redundancy for API servers. * Redundancy for API servers.
* *
@ -58,70 +92,28 @@ while (fallBackAPISources.length > 0) {
} }
} }
/** // Add default API
* Default configuration configStorage[''] = createConfig({
*/
const defaultConfig: IconifyAPIConfig = {
// API hosts
resources: ['https://api.iconify.design'].concat(fallBackAPI), resources: ['https://api.iconify.design'].concat(fallBackAPI),
}) as IconifyAPIConfig;
// Root path
path: '/',
// URL length limit
maxURL: 500,
// Timeout before next host is used.
rotate: 750,
// Timeout to retry same host.
timeout: 5000,
// Number of attempts for each host.
limit: 2,
// Randomise default API end point.
random: false,
// Start index
index: 0,
};
/** /**
* Local storage * Add custom config for provider
*/
const configStorage: ConfigStorage = {
default: defaultConfig,
prefixes: Object.create(null),
};
/**
* Add custom config for prefix(es)
*
* This function should be used before any API queries.
* On first API query computed configuration will be cached, so changes to config will not take effect.
*/ */
export function setAPIConfig( export function setAPIConfig(
customConfig: Partial<IconifyAPIConfig>, provider: string,
prefix?: string | string[] customConfig: PartialIconifyAPIConfig
): void { ): void {
const mergedConfig = merge( const config = createConfig(customConfig);
configStorage.default, if (config === null) {
customConfig
) as IconifyAPIConfig;
if (prefix === void 0) {
configStorage.default = mergedConfig;
return; return;
} }
(typeof prefix === 'string' ? [prefix] : prefix).forEach(prefix => { configStorage[provider] = config;
configStorage.prefixes[prefix] = mergedConfig;
});
} }
/** /**
* Get API configuration * Get API configuration
*/ */
export function getAPIConfig(prefix: string): IconifyAPIConfig | null { export function getAPIConfig(provider: string): IconifyAPIConfig | undefined {
const value = configStorage.prefixes[prefix]; return configStorage[provider];
return value === void 0 ? configStorage.default : value;
} }

View File

@ -15,8 +15,8 @@ import { getAPIModule } from './modules';
import { getAPIConfig, IconifyAPIConfig } from './config'; import { getAPIConfig, IconifyAPIConfig } from './config';
import { getStorage, addIconSet } from '../storage'; import { getStorage, addIconSet } from '../storage';
import { coreModules } from '../modules'; import { coreModules } from '../modules';
import { IconifyIconName } from '../icon/name'; import { IconifyIconName, IconifyIconSource } from '../icon/name';
import { listToIcons, getPrefixes } from '../icon/list'; import { listToIcons } from '../icon/list';
import { IconifyJSON } from '@iconify/types'; import { IconifyJSON } from '@iconify/types';
// Empty abort callback for loadIcons() // Empty abort callback for loadIcons()
@ -32,10 +32,13 @@ function emptyCallback(): void {
* either an icon or a missing icon. This way same icon should * either an icon or a missing icon. This way same icon should
* never be requested twice. * never be requested twice.
* *
* [prefix][icon] = time when icon was added to queue * [provider][prefix][icon] = time when icon was added to queue
*/ */
type PendingIcons = Record<string, number>; type PendingIcons = Record<string, number>;
const pendingIcons: Record<string, PendingIcons> = Object.create(null); const pendingIcons: Record<
string,
Record<string, PendingIcons>
> = Object.create(null);
/** /**
* List of icons that are waiting to be loaded. * List of icons that are waiting to be loaded.
@ -45,31 +48,39 @@ const pendingIcons: Record<string, PendingIcons> = Object.create(null);
* This list should not be used for any checks, use pendingIcons to check * This list should not be used for any checks, use pendingIcons to check
* if icons is being loaded. * if icons is being loaded.
* *
* [prefix] = array of icon names * [provider][prefix] = array of icon names
*/ */
const iconsToLoad: Record<string, string[]> = Object.create(null); const iconsToLoad: Record<string, Record<string, string[]>> = Object.create(
null
);
// Flags to merge multiple synchronous icon requests in one asynchronous request // Flags to merge multiple synchronous icon requests in one asynchronous request
const loaderFlags: Record<string, boolean> = Object.create(null); const loaderFlags: Record<string, Record<string, boolean>> = Object.create(
const queueFlags: Record<string, boolean> = Object.create(null); null
);
const queueFlags: Record<string, Record<string, boolean>> = Object.create(null);
// Redundancy instances cache // Redundancy instances cache, sorted by provider
interface LocalCache { interface LocalCache {
config: IconifyAPIConfig | null; config: IconifyAPIConfig;
redundancy: Redundancy | null; redundancy: Redundancy;
} }
const redundancyCache: Record<string, LocalCache> = Object.create(null); const redundancyCache: Record<string, LocalCache> = Object.create(null);
/** /**
* Function called when new icons have been loaded * Function called when new icons have been loaded
*/ */
function loadedNewIcons(prefix: string): void { function loadedNewIcons(provider: string, prefix: string): void {
// Run only once per tick, possibly joining multiple API responses in one call // Run only once per tick, possibly joining multiple API responses in one call
if (!loaderFlags[prefix]) { if (loaderFlags[provider] === void 0) {
loaderFlags[prefix] = true; loaderFlags[provider] = Object.create(null);
}
const providerLoaderFlags = loaderFlags[provider];
if (!providerLoaderFlags[prefix]) {
providerLoaderFlags[prefix] = true;
setTimeout(() => { setTimeout(() => {
loaderFlags[prefix] = false; providerLoaderFlags[prefix] = false;
updateCallbacks(prefix); updateCallbacks(provider, prefix);
}); });
} }
} }
@ -77,7 +88,7 @@ function loadedNewIcons(prefix: string): void {
/** /**
* Load icons * Load icons
*/ */
function loadNewIcons(prefix: string, icons: string[]): void { function loadNewIcons(provider: string, prefix: string, icons: string[]): void {
function err(): void { function err(): void {
console.error( console.error(
'Unable to retrieve icons for prefix "' + 'Unable to retrieve icons for prefix "' +
@ -86,68 +97,82 @@ function loadNewIcons(prefix: string, icons: string[]): void {
); );
} }
// Create nested objects if needed
if (iconsToLoad[provider] === void 0) {
iconsToLoad[provider] = Object.create(null);
}
const providerIconsToLoad = iconsToLoad[provider];
if (queueFlags[provider] === void 0) {
queueFlags[provider] = Object.create(null);
}
const providerQueueFlags = queueFlags[provider];
if (pendingIcons[provider] === void 0) {
pendingIcons[provider] = Object.create(null);
}
const providerPendingIcons = pendingIcons[provider];
// Add icons to queue // Add icons to queue
if (iconsToLoad[prefix] === void 0) { if (providerIconsToLoad[prefix] === void 0) {
iconsToLoad[prefix] = icons; providerIconsToLoad[prefix] = icons;
} else { } else {
iconsToLoad[prefix] = iconsToLoad[prefix].concat(icons).sort(); providerIconsToLoad[prefix] = providerIconsToLoad[prefix]
.concat(icons)
.sort();
} }
// Redundancy item
let cachedReundancy: LocalCache;
// Trigger update on next tick, mering multiple synchronous requests into one asynchronous request // Trigger update on next tick, mering multiple synchronous requests into one asynchronous request
if (!queueFlags[prefix]) { if (!providerQueueFlags[prefix]) {
queueFlags[prefix] = true; providerQueueFlags[prefix] = true;
setTimeout(() => { setTimeout(() => {
queueFlags[prefix] = false; providerQueueFlags[prefix] = false;
// Get icons and delete queue // Get icons and delete queue
const icons = iconsToLoad[prefix]; const icons = providerIconsToLoad[prefix];
delete iconsToLoad[prefix]; delete providerIconsToLoad[prefix];
// Get API module // Get API module
const api = getAPIModule(prefix); const api = getAPIModule(provider);
if (!api) { if (!api) {
// No way to load icons! // No way to load icons!
err(); err();
return; return;
} }
// Get Redundancy instance // Get API config and Redundancy instance
if (redundancyCache[prefix] === void 0) { if (cachedReundancy === void 0) {
const config = getAPIConfig(prefix); if (redundancyCache[provider] === void 0) {
const config = getAPIConfig(provider);
// Attempt to find matching instance from other prefixes if (!config) {
// Using same Redundancy instance allows keeping track of failed hosts for multiple prefixes // No way to load icons because configuration is not set!
for (const prefix2 in redundancyCache) { err();
const item = redundancyCache[prefix2]; return;
if (item.config === config) {
redundancyCache[prefix] = item;
break;
} }
}
if (redundancyCache[prefix] === void 0) { const redundancy = initRedundancy(config);
redundancyCache[prefix] = { cachedReundancy = {
config, config,
redundancy: config ? initRedundancy(config) : null, redundancy,
}; };
redundancyCache[provider] = cachedReundancy;
} else {
cachedReundancy = redundancyCache[provider];
} }
} }
const redundancy = redundancyCache[prefix].redundancy;
if (!redundancy) {
// No way to load icons because configuration is not set!
err();
return;
}
// Prepare parameters and run queries // Prepare parameters and run queries
const params = api.prepare(prefix, icons); const params = api.prepare(provider, prefix, icons);
params.forEach((item) => { params.forEach((item) => {
redundancy.query( cachedReundancy.redundancy.query(
item, item,
api.send as RedundancyQueryCallback, api.send as RedundancyQueryCallback,
(data) => { (data) => {
// Add icons to storage // Add icons to storage
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
try { try {
const added = addIconSet( const added = addIconSet(
storage, storage,
@ -159,21 +184,24 @@ function loadNewIcons(prefix: string, icons: string[]): void {
} }
// Remove added icons from pending list // Remove added icons from pending list
const pending = pendingIcons[prefix]; const pending = providerPendingIcons[prefix];
added.forEach((name) => { added.forEach((name) => {
delete pending[name]; delete pending[name];
}); });
// Cache API response // Cache API response
if (coreModules.cache) { if (coreModules.cache) {
coreModules.cache(data as IconifyJSON); coreModules.cache(
provider,
data as IconifyJSON
);
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
// Trigger update on next tick // Trigger update on next tick
loadedNewIcons(prefix); loadedNewIcons(provider, prefix);
} }
); );
}); });
@ -184,9 +212,11 @@ function loadNewIcons(prefix: string, icons: string[]): void {
/** /**
* Check if icon is being loaded * Check if icon is being loaded
*/ */
const isPending: IsPending = (prefix: string, icon: string): boolean => { const isPending: IsPending = (icon: IconifyIconName): boolean => {
return ( return (
pendingIcons[prefix] !== void 0 && pendingIcons[prefix][icon] !== void 0 pendingIcons[icon.provider] !== void 0 &&
pendingIcons[icon.provider][icon.prefix] !== void 0 &&
pendingIcons[icon.provider][icon.prefix][icon.name] !== void 0
); );
}; };
@ -225,16 +255,42 @@ const loadIcons: IconifyLoadIcons = (
}; };
} }
// Get all prefixes // Get all sources for pending icons
const prefixes = getPrefixes(sortedIcons.pending); const newIcons: Record<string, Record<string, string[]>> = Object.create(
null
);
const sources: IconifyIconSource[] = [];
let lastProvider: string, lastPrefix: string;
// Get pending icons queue for prefix and create new icons list sortedIcons.pending.forEach((icon) => {
const newIcons: Record<string, string[]> = Object.create(null); const provider = icon.provider;
prefixes.forEach((prefix) => { const prefix = icon.prefix;
if (pendingIcons[prefix] === void 0) { if (prefix === lastPrefix && provider === lastProvider) {
pendingIcons[prefix] = Object.create(null); return;
}
lastProvider = provider;
lastPrefix = prefix;
sources.push({
provider,
prefix,
});
if (pendingIcons[provider] === void 0) {
pendingIcons[provider] = Object.create(null);
}
const providerPendingIcons = pendingIcons[provider];
if (providerPendingIcons[prefix] === void 0) {
providerPendingIcons[prefix] = Object.create(null);
}
if (newIcons[provider] === void 0) {
newIcons[provider] = Object.create(null);
}
const providerNewIcons = newIcons[provider];
if (providerNewIcons[prefix] === void 0) {
providerNewIcons[prefix] = [];
} }
newIcons[prefix] = [];
}); });
// List of new icons // List of new icons
@ -244,29 +300,32 @@ const loadIcons: IconifyLoadIcons = (
// If icon was called before, it must exist in pendingIcons or storage, but because this // If icon was called before, it must exist in pendingIcons or storage, but because this
// function is called right after sortIcons() that checks storage, icon is definitely not in storage. // function is called right after sortIcons() that checks storage, icon is definitely not in storage.
sortedIcons.pending.forEach((icon) => { sortedIcons.pending.forEach((icon) => {
const provider = icon.provider;
const prefix = icon.prefix; const prefix = icon.prefix;
const name = icon.name; const name = icon.name;
const pendingQueue = pendingIcons[prefix]; const pendingQueue = pendingIcons[provider][prefix];
if (pendingQueue[name] === void 0) { if (pendingQueue[name] === void 0) {
// New icon - add to pending queue to mark it as being loaded // New icon - add to pending queue to mark it as being loaded
pendingQueue[name] = time; pendingQueue[name] = time;
// Add it to new icons list to pass it to API module for loading // Add it to new icons list to pass it to API module for loading
newIcons[prefix].push(name); newIcons[provider][prefix].push(name);
} }
}); });
// Load icons on next tick to make sure result is not returned before callback is stored and // Load icons on next tick to make sure result is not returned before callback is stored and
// to consolidate multiple synchronous loadIcons() calls into one asynchronous API call // to consolidate multiple synchronous loadIcons() calls into one asynchronous API call
prefixes.forEach((prefix) => { sources.forEach((source) => {
if (newIcons[prefix].length) { const provider = source.provider;
loadNewIcons(prefix, newIcons[prefix]); const prefix = source.prefix;
if (newIcons[provider][prefix].length) {
loadNewIcons(provider, prefix, newIcons[provider][prefix]);
} }
}); });
// Store callback and return abort function // Store callback and return abort function
return callback return callback
? storeCallback(callback, sortedIcons, prefixes) ? storeCallback(callback, sortedIcons, sources)
: emptyCallback; : emptyCallback;
}; };

View File

@ -4,6 +4,7 @@ import { RedundancyPendingItem } from '@cyberalien/redundancy';
* Params for sendQuery() * Params for sendQuery()
*/ */
export interface APIQueryParams { export interface APIQueryParams {
provider: string;
prefix: string; prefix: string;
icons: string[]; icons: string[];
} }
@ -12,6 +13,7 @@ export interface APIQueryParams {
* Functions to implement in module * Functions to implement in module
*/ */
export type IconifyAPIPrepareQuery = ( export type IconifyAPIPrepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
) => APIQueryParams[]; ) => APIQueryParams[];
@ -33,43 +35,36 @@ export interface IconifyAPIModule {
/** /**
* Local storate types and entries * Local storate types and entries
*/ */
interface ModuleStorage { interface ModulesStorage {
default: IconifyAPIModule | null; default?: IconifyAPIModule;
prefixes: Record<string, IconifyAPIModule>; providers: Record<string, IconifyAPIModule>;
} }
const storage: ModulesStorage = {
const storage: ModuleStorage = { providers: Object.create(null),
default: null,
prefixes: Object.create(null),
}; };
/** /**
* Set API module * Set default API module
*
* If prefix is not set, function sets default method.
* If prefix is a string or array of strings, function sets method only for those prefixes.
*
* This should be used before sending any API requests. If used after sending API request, method
* is already cached so changing callback will not have any effect.
*/ */
export function setAPIModule( export function setDefaultAPIModule(item: IconifyAPIModule): void {
item: IconifyAPIModule, storage.default = item;
prefix?: string | string[] }
): void {
if (prefix === void 0) {
storage.default = item;
return;
}
(typeof prefix === 'string' ? [prefix] : prefix).forEach(prefix => { /**
storage.prefixes[prefix] = item; * Set API module
}); */
export function setProviderAPIModule(
provider: string,
item: IconifyAPIModule
): void {
storage.providers[provider] = item;
} }
/** /**
* Get API module * Get API module
*/ */
export function getAPIModule(prefix: string): IconifyAPIModule | null { export function getAPIModule(provider: string): IconifyAPIModule | undefined {
const value = storage.prefixes[prefix]; return storage.providers[provider] === void 0
return value === void 0 ? storage.default : value; ? storage.default
: storage.providers[provider];
} }

View File

@ -13,13 +13,14 @@ type StorageEmptyList = StorageType<number[]>;
export interface StoredItem { export interface StoredItem {
cached: number; cached: number;
provider: string;
data: IconifyJSON; data: IconifyJSON;
} }
// After changing configuration change it in tests/*/fake_cache.ts // After changing configuration change it in tests/*/fake_cache.ts
// Cache version. Bump when structure changes // Cache version. Bump when structure changes
const cacheVersion = 'iconify1'; const cacheVersion = 'iconify2';
// Cache keys // Cache keys
const cachePrefix = 'iconify'; const cachePrefix = 'iconify';
@ -200,14 +201,16 @@ export const loadCache: LoadIconsCache = (): void => {
typeof data !== 'object' || typeof data !== 'object' ||
typeof data.cached !== 'number' || typeof data.cached !== 'number' ||
data.cached < minTime || data.cached < minTime ||
typeof data.provider !== 'string' ||
typeof data.data !== 'object' || typeof data.data !== 'object' ||
typeof data.data.prefix !== 'string' typeof data.data.prefix !== 'string'
) { ) {
valid = false; valid = false;
} else { } else {
// Add icon set // Add icon set
const provider = data.provider;
const prefix = data.data.prefix; const prefix = data.data.prefix;
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
valid = addIconSet(storage, data.data) as boolean; valid = addIconSet(storage, data.data) as boolean;
} }
} catch (err) { } catch (err) {
@ -263,7 +266,10 @@ export const loadCache: LoadIconsCache = (): void => {
/** /**
* Function to cache icons * Function to cache icons
*/ */
export const storeCache: CacheIcons = (data: IconifyJSON): void => { export const storeCache: CacheIcons = (
provider: string,
data: IconifyJSON
): void => {
if (!loaded) { if (!loaded) {
loadCache(); loadCache();
} }
@ -292,6 +298,7 @@ export const storeCache: CacheIcons = (data: IconifyJSON): void => {
try { try {
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data, data,
}; };
func.setItem(cachePrefix + index, JSON.stringify(item)); func.setItem(cachePrefix + index, JSON.stringify(item));

View File

@ -9,13 +9,14 @@ export function listToIcons(
): IconifyIconName[] { ): IconifyIconName[] {
const result: IconifyIconName[] = []; const result: IconifyIconName[] = [];
list.forEach(item => { list.forEach((item) => {
const icon: IconifyIconName = const icon: IconifyIconName =
typeof item === 'string' typeof item === 'string'
? (stringToIcon(item) as IconifyIconName) ? (stringToIcon(item) as IconifyIconName)
: item; : item;
if (!validate || validateIcon(icon)) { if (!validate || validateIcon(icon)) {
result.push({ result.push({
provider: icon.provider,
prefix: icon.prefix, prefix: icon.prefix,
name: icon.name, name: icon.name,
}); });
@ -26,12 +27,12 @@ export function listToIcons(
} }
/** /**
* Get all prefixes * Get all providers
*/ */
export function getPrefixes(list: IconifyIconName[]): string[] { export function getProviders(list: IconifyIconName[]): string[] {
const prefixes: Record<string, boolean> = Object.create(null); const providers: Record<string, boolean> = Object.create(null);
list.forEach(icon => { list.forEach((icon) => {
prefixes[icon.prefix] = true; providers[icon.provider] = true;
}); });
return Object.keys(prefixes); return Object.keys(providers);
} }

View File

@ -2,10 +2,16 @@
* Icon name * Icon name
*/ */
export interface IconifyIconName { export interface IconifyIconName {
readonly provider: string;
readonly prefix: string; readonly prefix: string;
readonly name: string; readonly name: string;
} }
/**
* Icon source: icon object without name
*/
export type IconifyIconSource = Omit<IconifyIconName, 'name'>;
/** /**
* Expression to test part of icon name. * Expression to test part of icon name.
*/ */
@ -15,22 +21,40 @@ const match = /^[a-z0-9]+(-[a-z0-9]+)*$/;
* Convert string to Icon object. * Convert string to Icon object.
*/ */
export const stringToIcon = (value: string): IconifyIconName | null => { export const stringToIcon = (value: string): IconifyIconName | null => {
// Attempt to split by colon: "prefix:name" let provider = '';
const colonSeparated = value.split(':'); const colonSeparated = value.split(':');
if (colonSeparated.length > 2) {
// Check for provider with correct '@' at start
if (value.slice(0, 1) === '@') {
// First part is provider
if (colonSeparated.length < 2 || colonSeparated.length > 3) {
// "@provider:prefix:name" or "@provider:prefix-name"
return null;
}
provider = (colonSeparated.shift() as string).slice(1);
}
// Check split by colon: "prefix:name", "provider:prefix:name"
if (colonSeparated.length > 3 || !colonSeparated.length) {
return null; return null;
} }
if (colonSeparated.length === 2) { if (colonSeparated.length > 1) {
// "prefix:name"
const name = colonSeparated.pop() as string;
const prefix = colonSeparated.pop() as string;
return { return {
prefix: colonSeparated[0], // Allow provider without '@': "provider:prefix:name"
name: colonSeparated[1], provider: colonSeparated.length > 0 ? colonSeparated[0] : provider,
prefix,
name,
}; };
} }
// Attempt to split by dash: "prefix-name" // Attempt to split by dash: "prefix-name"
const dashSeparated = value.split('-'); const dashSeparated = colonSeparated[0].split('-');
if (dashSeparated.length > 1) { if (dashSeparated.length > 1) {
return { return {
provider: provider,
prefix: dashSeparated.shift() as string, prefix: dashSeparated.shift() as string,
name: dashSeparated.join('-'), name: dashSeparated.join('-'),
}; };
@ -49,5 +73,9 @@ export const validateIcon = (icon: IconifyIconName | null): boolean => {
return false; return false;
} }
return !!(icon.prefix.match(match) && icon.name.match(match)); return !!(
(icon.provider === '' || icon.provider.match(match)) &&
icon.prefix.match(match) &&
icon.name.match(match)
);
}; };

View File

@ -19,35 +19,50 @@ export function sortIcons(icons: IconifyIconName[]): SortedIcons {
missing: [], missing: [],
pending: [], pending: [],
}; };
const storage: Record<string, IconStorage> = Object.create(null); const storage: Record<string, Record<string, IconStorage>> = Object.create(
null
);
// Sort icons alphabetically to prevent duplicates and make sure they are sorted in API queries // Sort icons alphabetically to prevent duplicates and make sure they are sorted in API queries
icons.sort((a, b) => { icons.sort((a, b) => {
if (a.prefix === b.prefix) { if (a.provider !== b.provider) {
return a.name.localeCompare(b.name); return a.provider.localeCompare(b.provider);
} }
return a.prefix.localeCompare(b.prefix); if (a.prefix !== b.prefix) {
return a.prefix.localeCompare(b.prefix);
}
return a.name.localeCompare(b.name);
}); });
let lastIcon: IconifyIconName = { let lastIcon: IconifyIconName = {
provider: '',
prefix: '', prefix: '',
name: '', name: '',
}; };
icons.forEach(icon => { icons.forEach((icon) => {
if (lastIcon.prefix === icon.prefix && lastIcon.name === icon.name) { if (
lastIcon.name === icon.name &&
lastIcon.prefix === icon.prefix &&
lastIcon.provider === icon.provider
) {
return; return;
} }
lastIcon = icon; lastIcon = icon;
// Check icon // Check icon
const provider = icon.provider;
const prefix = icon.prefix; const prefix = icon.prefix;
const name = icon.name; const name = icon.name;
if (storage[prefix] === void 0) { if (storage[provider] === void 0) {
storage[prefix] = getStorage(prefix); storage[provider] = Object.create(null);
} }
const providerStorage = storage[provider];
const localStorage = storage[prefix]; if (providerStorage[prefix] === void 0) {
providerStorage[prefix] = getStorage(provider, prefix);
}
const localStorage = providerStorage[prefix];
let list; let list;
if (localStorage.icons[name] !== void 0) { if (localStorage.icons[name] !== void 0) {
@ -59,6 +74,7 @@ export function sortIcons(icons: IconifyIconName[]): SortedIcons {
} }
const item: IconifyIconName = { const item: IconifyIconName = {
provider,
prefix, prefix,
name, name,
}; };

View File

@ -1,9 +1,10 @@
import { IconifyLoadIcons } from './loader'; import { IconifyLoadIcons } from './loader';
import { IconifyIconName } from '../icon/name';
/** /**
* Function to check if icon is pending * Function to check if icon is pending
*/ */
export type IsPending = (prefix: string, name: string) => boolean; export type IsPending = (icon: IconifyIconName) => boolean;
/** /**
* API interface * API interface

View File

@ -3,7 +3,7 @@ import { IconifyJSON } from '@iconify/types';
/** /**
* Function to cache loaded icons set * Function to cache loaded icons set
*/ */
export type CacheIcons = (data: IconifyJSON) => void; export type CacheIcons = (provider: string, data: IconifyJSON) => void;
/** /**
* Function to load icons from cache * Function to load icons from cache

View File

@ -24,21 +24,25 @@ type IconRecords = Record<string, FullIconifyIcon | null>;
* Storage type * Storage type
*/ */
export interface IconStorage { export interface IconStorage {
provider: string;
prefix: string; prefix: string;
icons: IconRecords; icons: IconRecords;
missing: Record<string, number>; missing: Record<string, number>;
} }
/** /**
* Storage by prefix * Storage by provider and prefix
*/ */
const storage: Record<string, IconStorage> = Object.create(null); const storage: Record<string, Record<string, IconStorage>> = Object.create(
null
);
/** /**
* Create new storage * Create new storage
*/ */
export function newStorage(prefix: string): IconStorage { export function newStorage(provider: string, prefix: string): IconStorage {
return { return {
provider,
prefix, prefix,
icons: Object.create(null), icons: Object.create(null),
missing: Object.create(null), missing: Object.create(null),
@ -46,20 +50,31 @@ export function newStorage(prefix: string): IconStorage {
} }
/** /**
* Get storage for prefix * Get storage for provider and prefix
*/ */
export function getStorage(prefix: string): IconStorage { export function getStorage(provider: string, prefix: string): IconStorage {
if (storage[prefix] === void 0) { if (storage[provider] === void 0) {
storage[prefix] = newStorage(prefix); storage[provider] = Object.create(null);
} }
return storage[prefix]; const providerStorage = storage[provider];
if (providerStorage[prefix] === void 0) {
providerStorage[prefix] = newStorage(provider, prefix);
}
return providerStorage[prefix];
}
/**
* Get all providers
*/
export function listStoredProviders(): string[] {
return Object.keys(storage);
} }
/** /**
* Get all prefixes * Get all prefixes
*/ */
export function listStoredPrefixes(): string[] { export function listStoredPrefixes(provider: string): string[] {
return Object.keys(storage); return storage[provider] === void 0 ? [] : Object.keys(storage[provider]);
} }
/** /**
@ -119,7 +134,7 @@ export function addIconSet(
// Check for missing icons list returned by API // Check for missing icons list returned by API
if (data.not_found instanceof Array) { if (data.not_found instanceof Array) {
const t = Date.now(); const t = Date.now();
data.not_found.forEach(name => { data.not_found.forEach((name) => {
storage.missing[name] = t; storage.missing[name] = t;
if (list === 'all') { if (list === 'all') {
added.push(name); added.push(name);
@ -134,7 +149,7 @@ export function addIconSet(
// Get default values // Get default values
const defaults = Object.create(null); const defaults = Object.create(null);
defaultsKeys.forEach(key => { defaultsKeys.forEach((key) => {
if (data[key] !== void 0 && typeof data[key] !== 'object') { if (data[key] !== void 0 && typeof data[key] !== 'object') {
defaults[key] = data[key]; defaults[key] = data[key];
} }
@ -142,7 +157,7 @@ export function addIconSet(
// Get icons // Get icons
const icons = data.icons; const icons = data.icons;
Object.keys(icons).forEach(name => { Object.keys(icons).forEach((name) => {
const icon = icons[name]; const icon = icons[name];
if (typeof icon.body !== 'string') { if (typeof icon.body !== 'string') {
throw new Error('Invalid icon'); throw new Error('Invalid icon');
@ -160,7 +175,7 @@ export function addIconSet(
// Get aliases // Get aliases
if (typeof data.aliases === 'object') { if (typeof data.aliases === 'object') {
const aliases = data.aliases; const aliases = data.aliases;
Object.keys(aliases).forEach(name => { Object.keys(aliases).forEach((name) => {
const icon = resolveAlias(aliases[name], icons, aliases, 1); const icon = resolveAlias(aliases[name], icons, aliases, 1);
if (icon) { if (icon) {
// Freeze icon to make sure it will not be modified // Freeze icon to make sure it will not be modified

View File

@ -7,12 +7,13 @@ import {
} from '../../lib/icon/name'; } from '../../lib/icon/name';
describe('Testing icon name', () => { describe('Testing icon name', () => {
it('Converting and validating', () => { it('Simple icon names', () => {
let icon; let icon;
// Simple prefix-name // Simple prefix-name
icon = stringToIcon('fa-home') as IconifyIconName; icon = stringToIcon('fa-home') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'fa', prefix: 'fa',
name: 'home', name: 'home',
}); });
@ -21,6 +22,7 @@ describe('Testing icon name', () => {
// Simple prefix:name // Simple prefix:name
icon = stringToIcon('fa:arrow-left') as IconifyIconName; icon = stringToIcon('fa:arrow-left') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'fa', prefix: 'fa',
name: 'arrow-left', name: 'arrow-left',
}); });
@ -29,11 +31,17 @@ describe('Testing icon name', () => {
// Longer prefix:name // Longer prefix:name
icon = stringToIcon('mdi-light:home-outline') as IconifyIconName; icon = stringToIcon('mdi-light:home-outline') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'mdi-light', prefix: 'mdi-light',
name: 'home-outline', name: 'home-outline',
}); });
expect(validateIcon(icon)).to.be.equal(true); expect(validateIcon(icon)).to.be.equal(true);
// Missing icon name
icon = stringToIcon('@iconify-home-icon');
expect(icon).to.be.eql(null);
expect(validateIcon(icon)).to.be.equal(false);
// Underscore is not an acceptable separator // Underscore is not an acceptable separator
icon = stringToIcon('fa_home'); icon = stringToIcon('fa_home');
expect(icon).to.be.eql(null); expect(icon).to.be.eql(null);
@ -42,19 +50,21 @@ describe('Testing icon name', () => {
// Invalid character '_': fail validateIcon // Invalid character '_': fail validateIcon
icon = stringToIcon('fa:home_outline') as IconifyIconName; icon = stringToIcon('fa:home_outline') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'fa', prefix: 'fa',
name: 'home_outline', name: 'home_outline',
}); });
expect(validateIcon(icon)).to.be.equal(false); expect(validateIcon(icon)).to.be.equal(false);
// Too many colons: fail stringToIcon // Too many colons: fail stringToIcon
icon = stringToIcon('mdi-light:home:outline'); icon = stringToIcon('mdi:light:home:outline');
expect(icon).to.be.eql(null); expect(icon).to.be.eql(null);
expect(validateIcon(icon)).to.be.equal(false); expect(validateIcon(icon)).to.be.equal(false);
// Upper case: fail validateIcon // Upper case: fail validateIcon
icon = stringToIcon('MD:Home') as IconifyIconName; icon = stringToIcon('MD:Home') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'MD', prefix: 'MD',
name: 'Home', name: 'Home',
}); });
@ -63,6 +73,7 @@ describe('Testing icon name', () => {
// Numbers: pass // Numbers: pass
icon = stringToIcon('1:foo') as IconifyIconName; icon = stringToIcon('1:foo') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: '1', prefix: '1',
name: 'foo', name: 'foo',
}); });
@ -71,9 +82,68 @@ describe('Testing icon name', () => {
// Accented letters: fail validateIcon // Accented letters: fail validateIcon
icon = stringToIcon('md-fõö') as IconifyIconName; icon = stringToIcon('md-fõö') as IconifyIconName;
expect(icon).to.be.eql({ expect(icon).to.be.eql({
provider: '',
prefix: 'md', prefix: 'md',
name: 'fõö', name: 'fõö',
}); });
expect(validateIcon(icon)).to.be.equal(false); expect(validateIcon(icon)).to.be.equal(false);
}); });
it('Providers', () => {
let icon;
// Simple @provider:prefix-name
icon = stringToIcon('@iconify:fa-home') as IconifyIconName;
expect(icon).to.be.eql({
provider: 'iconify',
prefix: 'fa',
name: 'home',
});
expect(validateIcon(icon)).to.be.equal(true);
// Simple @provider:prefix:name
icon = stringToIcon('@iconify:fa:arrow-left') as IconifyIconName;
expect(icon).to.be.eql({
provider: 'iconify',
prefix: 'fa',
name: 'arrow-left',
});
expect(validateIcon(icon)).to.be.equal(true);
// Longer @provider:prefix:name
icon = stringToIcon(
'@iconify-backup:mdi-light:home-outline'
) as IconifyIconName;
expect(icon).to.be.eql({
provider: 'iconify-backup',
prefix: 'mdi-light',
name: 'home-outline',
});
expect(validateIcon(icon)).to.be.equal(true);
// Missing @ for provider
icon = stringToIcon(
'iconify-backup:mdi-light:home-outline'
) as IconifyIconName;
expect(icon).to.be.eql({
provider: 'iconify-backup',
prefix: 'mdi-light',
name: 'home-outline',
});
expect(validateIcon(icon)).to.be.equal(true);
// Too many colons: fail stringToIcon
icon = stringToIcon('@mdi:light:home:outline');
expect(icon).to.be.eql(null);
expect(validateIcon(icon)).to.be.equal(false);
// Upper case: fail validateIcon
icon = stringToIcon('@MD:home-outline') as IconifyIconName;
expect(icon).to.be.eql({
provider: 'MD',
prefix: 'home',
name: 'outline',
});
expect(validateIcon(icon)).to.be.equal(false);
});
}); });

View File

@ -13,7 +13,7 @@ import { IconifyJSON } from '@iconify/types';
describe('Testing storage', () => { describe('Testing storage', () => {
it('Adding icon', () => { it('Adding icon', () => {
const storage = newStorage('foo'); const storage = newStorage('', 'foo');
// Add one icon // Add one icon
addIcon(storage, 'test', { addIcon(storage, 'test', {
@ -82,7 +82,7 @@ describe('Testing storage', () => {
}); });
it('Adding simple icon set', () => { it('Adding simple icon set', () => {
const storage = newStorage('foo'); const storage = newStorage('', 'foo');
// Add two icons // Add two icons
expect( expect(
@ -138,7 +138,7 @@ describe('Testing storage', () => {
}); });
it('Icon set with invalid default values', () => { it('Icon set with invalid default values', () => {
const storage = newStorage('foo'); const storage = newStorage('', 'foo');
// Missing prefix, invalid default values // Missing prefix, invalid default values
expect( expect(
@ -205,7 +205,7 @@ describe('Testing storage', () => {
}); });
it('Icon set with simple aliases', () => { it('Icon set with simple aliases', () => {
const storage = newStorage('foo'); const storage = newStorage('', 'foo');
expect( expect(
addIconSet(storage, { addIconSet(storage, {
@ -275,7 +275,7 @@ describe('Testing storage', () => {
}); });
it('Icon set with nested aliases', () => { it('Icon set with nested aliases', () => {
const storage = newStorage('foo'); const storage = newStorage('', 'foo');
expect( expect(
addIconSet(storage, { addIconSet(storage, {
@ -402,7 +402,7 @@ describe('Testing storage', () => {
}); });
it('Icon set with aliases that use transformations', () => { it('Icon set with aliases that use transformations', () => {
const storage = newStorage('arty-animated'); const storage = newStorage('iconify', 'arty-animated');
const iconBody = const iconBody =
'<g stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" fill="none" fill-rule="evenodd"><path d="M40 64l48-48" class="animation-delay-0 animation-duration-10 animate-stroke stroke-length-102"/><path d="M40 64l48 48" class="animation-delay-0 animation-duration-10 animate-stroke stroke-length-102"/></g>'; '<g stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" fill="none" fill-rule="evenodd"><path d="M40 64l48-48" class="animation-delay-0 animation-duration-10 animate-stroke stroke-length-102"/><path d="M40 64l48 48" class="animation-delay-0 animation-duration-10 animate-stroke stroke-length-102"/></g>';

View File

@ -18,11 +18,12 @@ describe('Testing API callbacks', () => {
return 'api-cb-test-' + (prefixCounter < 10 ? '0' : '') + prefixCounter; return 'api-cb-test-' + (prefixCounter < 10 ? '0' : '') + prefixCounter;
} }
it('Simple callback', done => { it('Simple callback', (done) => {
const provider = 'iconify';
const prefix = nextPrefix(); const prefix = nextPrefix();
let counter = 0; let counter = 0;
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
const abort = storeCallback( const abort = storeCallback(
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
expect(unsubscribe).to.be.equal(abort); expect(unsubscribe).to.be.equal(abort);
@ -33,23 +34,28 @@ describe('Testing API callbacks', () => {
// First run - icon1 should be loaded, icon3 should be missing // First run - icon1 should be loaded, icon3 should be missing
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
]); ]);
expect(missing).to.be.eql([ expect(missing).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]); ]);
expect(pending).to.be.eql([ expect(pending).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
]); ]);
expect(callbacks[prefix].length).to.be.equal(1); expect(callbacks[provider][prefix].length).to.be.equal(
1
);
// Add icon2 and trigger update // Add icon2 and trigger update
addIconSet(storage, { addIconSet(storage, {
@ -61,54 +67,67 @@ describe('Testing API callbacks', () => {
}, },
}); });
updateCallbacks(prefix); updateCallbacks(provider, prefix);
return; return;
case 2: case 2:
// Second run - icon2 should be added, completing callback // Second run - icon2 should be added, completing callback
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
]); ]);
expect(missing).to.be.eql([ expect(missing).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]); ]);
expect(pending).to.be.eql([]); expect(pending).to.be.eql([]);
expect(callbacks[prefix].length).to.be.equal(0); expect(callbacks[provider][prefix].length).to.be.equal(
0
);
done(); done();
} }
}, },
sortIcons([ sortIcons([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]), ]),
[prefix] [
{
provider,
prefix,
},
]
); );
// Test callbacks // Test callbacks
expect(callbacks[prefix].length).to.be.equal(1); expect(callbacks[provider][prefix].length).to.be.equal(1);
// Test update - should do nothing // Test update - should do nothing
updateCallbacks(prefix); updateCallbacks(provider, prefix);
// Wait for tick because updateCallbacks will use one // Wait for tick because updateCallbacks will use one
setTimeout(() => { setTimeout(() => {
@ -125,14 +144,15 @@ describe('Testing API callbacks', () => {
}, },
not_found: ['icon3'], not_found: ['icon3'],
}); });
updateCallbacks(prefix); updateCallbacks(provider, prefix);
}); });
}); });
it('Callback that should not be stored', () => { it('Callback that should not be stored', () => {
const provider = '';
const prefix = nextPrefix(); const prefix = nextPrefix();
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
addIconSet(storage, { addIconSet(storage, {
prefix, prefix,
icons: { icons: {
@ -152,30 +172,39 @@ describe('Testing API callbacks', () => {
}, },
sortIcons([ sortIcons([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]), ]),
[prefix] [
{
provider,
prefix,
},
]
); );
// callbacks should not have been initialised // callbacks should not have been initialised
expect(callbacks[prefix]).to.be.equal(void 0); expect(callbacks[prefix]).to.be.equal(void 0);
}); });
it('Cancel callback', done => { it('Cancel callback', (done) => {
const provider = 'foo';
const prefix = nextPrefix(); const prefix = nextPrefix();
let counter = 0; let counter = 0;
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
const abort = storeCallback( const abort = storeCallback(
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
expect(unsubscribe).to.be.equal(abort); expect(unsubscribe).to.be.equal(abort);
@ -186,23 +215,26 @@ describe('Testing API callbacks', () => {
// First run - icon1 should be loaded, icon3 should be missing // First run - icon1 should be loaded, icon3 should be missing
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
]); ]);
expect(missing).to.be.eql([ expect(missing).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]); ]);
expect(pending).to.be.eql([ expect(pending).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
]); ]);
expect(callbacks[prefix].length).to.be.equal(1); expect(callbacks[provider][prefix].length).to.be.equal(1);
// Add icon2 and trigger update // Add icon2 and trigger update
addIconSet(storage, { addIconSet(storage, {
@ -214,35 +246,43 @@ describe('Testing API callbacks', () => {
}, },
}); });
updateCallbacks(prefix); updateCallbacks(provider, prefix);
// Unsubscribe and set timer to call done() // Unsubscribe and set timer to call done()
unsubscribe(); unsubscribe();
expect(callbacks[prefix].length).to.be.equal(0); expect(callbacks[provider][prefix].length).to.be.equal(0);
setTimeout(done); setTimeout(done);
}, },
sortIcons([ sortIcons([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
]), ]),
[prefix] [
{
provider,
prefix,
},
]
); );
// Test callbacks // Test callbacks
expect(callbacks[prefix].length).to.be.equal(1); expect(callbacks[provider][prefix].length).to.be.equal(1);
// Test update - should do nothing // Test update - should do nothing
updateCallbacks(prefix); updateCallbacks(provider, prefix);
// Wait for tick because updateCallbacks will use one // Wait for tick because updateCallbacks will use one
setTimeout(() => { setTimeout(() => {
@ -259,17 +299,18 @@ describe('Testing API callbacks', () => {
}, },
not_found: ['icon3'], not_found: ['icon3'],
}); });
updateCallbacks(prefix); updateCallbacks(provider, prefix);
}); });
}); });
it('Multiple prefixes', done => { it('Multiple prefixes', (done) => {
const provider = '';
const prefix1 = nextPrefix(); const prefix1 = nextPrefix();
const prefix2 = nextPrefix(); const prefix2 = nextPrefix();
let counter = 0; let counter = 0;
const storage1 = getStorage(prefix1); const storage1 = getStorage(provider, prefix1);
const storage2 = getStorage(prefix2); const storage2 = getStorage(provider, prefix2);
const abort = storeCallback( const abort = storeCallback(
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
@ -281,24 +322,31 @@ describe('Testing API callbacks', () => {
// First run - icon1 should be loaded, icon3 should be missing // First run - icon1 should be loaded, icon3 should be missing
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix: prefix1, prefix: prefix1,
name: 'icon1', name: 'icon1',
}, },
]); ]);
expect(missing).to.be.eql([ expect(missing).to.be.eql([
{ {
provider,
prefix: prefix1, prefix: prefix1,
name: 'icon3', name: 'icon3',
}, },
]); ]);
expect(pending).to.be.eql([ expect(pending).to.be.eql([
{ {
provider,
prefix: prefix2, prefix: prefix2,
name: 'icon2', name: 'icon2',
}, },
]); ]);
expect(callbacks[prefix1].length).to.be.equal(0); expect(callbacks[provider][prefix1].length).to.be.equal(
expect(callbacks[prefix2].length).to.be.equal(1); 0
);
expect(callbacks[provider][prefix2].length).to.be.equal(
1
);
// Add icon2 and trigger update // Add icon2 and trigger update
addIconSet(storage2, { addIconSet(storage2, {
@ -310,13 +358,17 @@ describe('Testing API callbacks', () => {
}, },
}); });
updateCallbacks(prefix2); updateCallbacks(provider, prefix2);
break; break;
case 2: case 2:
// Second run - icon2 should be loaded // Second run - icon2 should be loaded
expect(callbacks[prefix1].length).to.be.equal(0); expect(callbacks[provider][prefix1].length).to.be.equal(
expect(callbacks[prefix2].length).to.be.equal(0); 0
);
expect(callbacks[provider][prefix2].length).to.be.equal(
0
);
done(); done();
break; break;
@ -326,27 +378,33 @@ describe('Testing API callbacks', () => {
}, },
sortIcons([ sortIcons([
{ {
provider,
prefix: prefix1, prefix: prefix1,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix: prefix2, prefix: prefix2,
name: 'icon2', name: 'icon2',
}, },
{ {
provider,
prefix: prefix1, prefix: prefix1,
name: 'icon3', name: 'icon3',
}, },
]), ]),
[prefix1, prefix2] [
{ provider, prefix: prefix1 },
{ provider, prefix: prefix2 },
]
); );
// Test callbacks // Test callbacks
expect(callbacks[prefix1].length).to.be.equal(1); expect(callbacks[provider][prefix1].length).to.be.equal(1);
expect(callbacks[prefix2].length).to.be.equal(1); expect(callbacks[provider][prefix2].length).to.be.equal(1);
// Test update - should do nothing // Test update - should do nothing
updateCallbacks(prefix1); updateCallbacks(provider, prefix1);
// Wait for tick because updateCallbacks will use one // Wait for tick because updateCallbacks will use one
setTimeout(() => { setTimeout(() => {
@ -363,7 +421,150 @@ describe('Testing API callbacks', () => {
}, },
not_found: ['icon3'], not_found: ['icon3'],
}); });
updateCallbacks(prefix1); updateCallbacks(provider, prefix1);
});
});
it('Multiple providers', (done) => {
const provider1 = nextPrefix();
const provider2 = nextPrefix();
const prefix1 = nextPrefix();
const prefix2 = nextPrefix();
let counter = 0;
const storage1 = getStorage(provider1, prefix1);
const storage2 = getStorage(provider2, prefix2);
const abort = storeCallback(
(loaded, missing, pending, unsubscribe) => {
expect(unsubscribe).to.be.equal(abort);
counter++;
switch (counter) {
case 1:
// First run - icon1 should be loaded, icon3 should be missing
expect(loaded).to.be.eql([
{
provider: provider1,
prefix: prefix1,
name: 'icon1',
},
]);
expect(missing).to.be.eql([
{
provider: provider1,
prefix: prefix1,
name: 'icon3',
},
]);
expect(pending).to.be.eql([
{
provider: provider2,
prefix: prefix2,
name: 'icon2',
},
]);
expect(
callbacks[provider1][prefix1].length
).to.be.equal(0);
expect(
callbacks[provider2][prefix2].length
).to.be.equal(1);
// Make sure providers/prefixes aren't mixed
expect(callbacks[provider1][prefix2]).to.be.equal(
void 0
);
expect(callbacks[provider2][prefix1]).to.be.equal(
void 0
);
// Add icon2 and trigger update
addIconSet(storage2, {
prefix: prefix2,
icons: {
icon2: {
body: '<g></g>',
},
},
});
updateCallbacks(provider2, prefix2);
break;
case 2:
// Second run - icon2 should be loaded
expect(
callbacks[provider1][prefix1].length
).to.be.equal(0);
expect(
callbacks[provider2][prefix2].length
).to.be.equal(0);
// Make sure providers/prefixes aren't mixed
expect(callbacks[provider1][prefix2]).to.be.equal(
void 0
);
expect(callbacks[provider2][prefix1]).to.be.equal(
void 0
);
done();
break;
default:
done('Callback was called ' + counter + ' times.');
}
},
sortIcons([
{
provider: provider1,
prefix: prefix1,
name: 'icon1',
},
{
provider: provider2,
prefix: prefix2,
name: 'icon2',
},
{
provider: provider1,
prefix: prefix1,
name: 'icon3',
},
]),
[
{ provider: provider1, prefix: prefix1 },
{ provider: provider2, prefix: prefix2 },
]
);
// Test callbacks
expect(callbacks[provider1][prefix1].length).to.be.equal(1);
expect(callbacks[provider2][prefix2].length).to.be.equal(1);
expect(callbacks[provider1][prefix2]).to.be.equal(void 0);
expect(callbacks[provider2][prefix1]).to.be.equal(void 0);
// Test update - should do nothing
updateCallbacks(provider1, prefix1);
// Wait for tick because updateCallbacks will use one
setTimeout(() => {
// Callback should not have been called yet
expect(counter).to.be.equal(0);
// Add few icons and run updateCallbacks
addIconSet(storage1, {
prefix: prefix1,
icons: {
icon1: {
body: '<g></g>',
},
},
not_found: ['icon3'],
});
updateCallbacks(provider1, prefix1);
}); });
}); });
}); });

View File

@ -9,7 +9,7 @@ import {
IconifyAPIConfig, IconifyAPIConfig,
} from '../../lib/api/config'; } from '../../lib/api/config';
import { import {
setAPIModule, setProviderAPIModule,
APIQueryParams, APIQueryParams,
getAPIModule, getAPIModule,
IconifyAPIModule, IconifyAPIModule,
@ -25,10 +25,12 @@ describe('Testing API modules', () => {
} }
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons, icons,
}; };
@ -44,42 +46,36 @@ describe('Testing API modules', () => {
}; };
it('Empty module', () => { it('Empty module', () => {
const prefix = nextPrefix(); const provider = nextPrefix();
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://localhost:3000'],
resources: ['https://localhost:3000'], maxURL: 500,
maxURL: 500, });
},
prefix
);
// Set fake module // Set fake module
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
prefix
);
// Get config // Get config
const config = getAPIConfig(prefix) as IconifyAPIConfig; const config = getAPIConfig(provider) as IconifyAPIConfig;
expect(config).to.not.be.equal(null); expect(config).to.not.be.equal(void 0);
// Check setAPIConfig // Check setAPIConfig
expect(config.resources).to.be.eql(['https://localhost:3000']); expect(config.resources).to.be.eql(['https://localhost:3000']);
// Check getAPIModule() // Check getAPIModule()
const item = getAPIModule(prefix) as IconifyAPIModule; const item = getAPIModule(provider) as IconifyAPIModule;
expect(item).to.not.be.equal(null); expect(item).to.not.be.equal(void 0);
expect(item.prepare).to.be.equal(prepareQuery); expect(item.prepare).to.be.equal(prepareQuery);
expect(item.send).to.be.equal(sendQuery); expect(item.send).to.be.equal(sendQuery);
// Get module for different prefix to make sure it is empty // Get module for different provider to make sure it is empty
const prefix2 = nextPrefix(); const provider2 = nextPrefix();
const item2 = getAPIModule(prefix2); const item2 = getAPIModule(provider2);
expect(item2).to.be.equal(null); expect(item2).to.be.equal(void 0);
}); });
}); });

View File

@ -3,8 +3,8 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { RedundancyPendingItem } from '@cyberalien/redundancy'; import { RedundancyPendingItem } from '@cyberalien/redundancy';
import { setAPIConfig } from '../../lib/api/config'; import { setAPIConfig, IconifyAPIConfig } from '../../lib/api/config';
import { setAPIModule, APIQueryParams } from '../../lib/api/modules'; import { setProviderAPIModule, APIQueryParams } from '../../lib/api/modules';
import { API } from '../../lib/api/'; import { API } from '../../lib/api/';
describe('Testing API loadIcons', () => { describe('Testing API loadIcons', () => {
@ -16,24 +16,24 @@ describe('Testing API loadIcons', () => {
); );
} }
it('Loading few icons', done => { it('Loading few icons', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
let asyncCounter = 0; let asyncCounter = 0;
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
},
prefix
);
// Icon loader // Icon loader
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons, icons,
}; };
@ -44,6 +44,7 @@ describe('Testing API loadIcons', () => {
// Test input and return as one item // Test input and return as one item
const expected: APIQueryParams = { const expected: APIQueryParams = {
provider,
prefix, prefix,
icons: ['icon1', 'icon2'], icons: ['icon1', 'icon2'],
}; };
@ -64,6 +65,7 @@ describe('Testing API loadIcons', () => {
// Test input // Test input
expect(host).to.be.equal('https://api1.local'); expect(host).to.be.equal('https://api1.local');
const expected: APIQueryParams = { const expected: APIQueryParams = {
provider,
prefix, prefix,
icons: ['icon1', 'icon2'], icons: ['icon1', 'icon2'],
}; };
@ -86,24 +88,22 @@ describe('Testing API loadIcons', () => {
expect(asyncCounter).to.be.equal(3); expect(asyncCounter).to.be.equal(3);
}; };
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
prefix
);
// Load icons // Load icons
API.loadIcons( API.loadIcons(
[ [
// as icon // as icon
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
// as string // as string
prefix + ':icon2', provider + ':' + prefix + ':icon2',
], ],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// This callback should be called last // This callback should be called last
@ -112,10 +112,12 @@ describe('Testing API loadIcons', () => {
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -123,42 +125,54 @@ describe('Testing API loadIcons', () => {
expect(missing).to.be.eql([]); expect(missing).to.be.eql([]);
expect(pending).to.be.eql([]); expect(pending).to.be.eql([]);
expect(API.isPending(prefix, 'icon1')).to.be.equal(false); expect(
expect(API.isPending(prefix, 'icon3')).to.be.equal(false); API.isPending({
provider,
prefix,
name: 'icon1',
})
).to.be.equal(false);
expect(
API.isPending({ provider, prefix, name: 'icon3' })
).to.be.equal(false);
done(); done();
} }
); );
// Test isPending // Test isPending
expect(API.isPending(prefix, 'icon1')).to.be.equal(true); expect(API.isPending({ provider, prefix, name: 'icon1' })).to.be.equal(
expect(API.isPending(prefix, 'icon3')).to.be.equal(false); true
);
expect(API.isPending({ provider, prefix, name: 'icon3' })).to.be.equal(
false
);
// Make sure asyncCounter wasn't increased because loading shoud happen on next tick // Make sure asyncCounter wasn't increased because loading shoud happen on next tick
expect(asyncCounter).to.be.equal(0); expect(asyncCounter).to.be.equal(0);
asyncCounter++; asyncCounter++;
}); });
it('Split results', done => { it('Split results', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], });
},
prefix
);
// Icon loader // Icon loader
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
// Split all icons in multiple queries, one icon per query // Split all icons in multiple queries, one icon per query
const results: APIQueryParams[] = []; const results: APIQueryParams[] = [];
icons.forEach(icon => { icons.forEach((icon) => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons: [icon], icons: [icon],
}; };
@ -182,6 +196,7 @@ describe('Testing API loadIcons', () => {
// Icon names should match queryCounter: 'icon1' on first run, 'icon2' on second run // Icon names should match queryCounter: 'icon1' on first run, 'icon2' on second run
queryCounter++; queryCounter++;
const expected: APIQueryParams = { const expected: APIQueryParams = {
provider,
prefix, prefix,
icons: ['icon' + queryCounter], icons: ['icon' + queryCounter],
}; };
@ -189,7 +204,7 @@ describe('Testing API loadIcons', () => {
// Send only requested icons // Send only requested icons
const icons = Object.create(null); const icons = Object.create(null);
params.icons.forEach(icon => { params.icons.forEach((icon) => {
icons[icon] = { icons[icon] = {
body: '<path d="" />', body: '<path d="" />',
}; };
@ -200,18 +215,18 @@ describe('Testing API loadIcons', () => {
}); });
}; };
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
prefix
);
// Load icons // Load icons
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once because results should be sent in same tick // Callback should be called only once because results should be sent in same tick
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -220,10 +235,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -235,24 +252,24 @@ describe('Testing API loadIcons', () => {
); );
}); });
it('Fail on default host', done => { it('Fail on default host', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], rotate: 100, // 100ms to speed up test
rotate: 100, // 100ms to speed up test });
},
prefix
);
// Icon loader // Icon loader
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons, icons,
}; };
@ -299,18 +316,18 @@ describe('Testing API loadIcons', () => {
} }
}; };
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
prefix
);
// Load icons // Load icons
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once // Callback should be called only once
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -319,10 +336,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -335,24 +354,24 @@ describe('Testing API loadIcons', () => {
); );
}); });
it('Fail on default host, multiple queries', done => { it('Fail on default host, multiple queries', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], rotate: 100, // 100ms to speed up test
rotate: 100, // 100ms to speed up test });
},
prefix
);
// Icon loader // Icon loader
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons, icons,
}; };
@ -420,18 +439,18 @@ describe('Testing API loadIcons', () => {
} }
}; };
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
prefix
);
// Load icons // Load icons
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once // Callback should be called only once
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -440,10 +459,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -455,7 +476,10 @@ describe('Testing API loadIcons', () => {
setTimeout(() => { setTimeout(() => {
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix + ':icon3', prefix + ':icon4'], [
provider + ':' + prefix + ':icon3',
provider + ':' + prefix + ':icon4',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once // Callback should be called only once
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -464,10 +488,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon3', name: 'icon3',
}, },
{ {
provider,
prefix, prefix,
name: 'icon4', name: 'icon4',
}, },
@ -483,25 +509,25 @@ describe('Testing API loadIcons', () => {
); );
}); });
it('Fail on default host, multiple queries with different prefixes', done => { it('Fail on default host, multiple queries with different prefixes', (done) => {
const provider = nextPrefix();
const prefix = nextPrefix(); const prefix = nextPrefix();
const prefix2 = nextPrefix(); const prefix2 = nextPrefix();
// Set config // Set config
setAPIConfig( setAPIConfig(provider, {
{ resources: ['https://api1.local', 'https://api2.local'],
resources: ['https://api1.local', 'https://api2.local'], rotate: 100, // 100ms to speed up test
rotate: 100, // 100ms to speed up test });
},
[prefix, prefix2]
);
// Icon loader // Icon loader
const prepareQuery = ( const prepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
const item: APIQueryParams = { const item: APIQueryParams = {
provider,
prefix, prefix,
icons, icons,
}; };
@ -572,18 +598,18 @@ describe('Testing API loadIcons', () => {
} }
}; };
setAPIModule( setProviderAPIModule(provider, {
{ prepare: prepareQuery,
prepare: prepareQuery, send: sendQuery,
send: sendQuery, });
},
[prefix, prefix2]
);
// Load icons // Load icons
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix + ':icon1', prefix + ':icon2'], [
provider + ':' + prefix + ':icon1',
provider + ':' + prefix + ':icon2',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once // Callback should be called only once
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -592,10 +618,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix, prefix,
name: 'icon1', name: 'icon1',
}, },
{ {
provider,
prefix, prefix,
name: 'icon2', name: 'icon2',
}, },
@ -607,7 +635,10 @@ describe('Testing API loadIcons', () => {
setTimeout(() => { setTimeout(() => {
let callbackCalled = false; let callbackCalled = false;
API.loadIcons( API.loadIcons(
[prefix2 + ':icon2', prefix2 + ':icon4'], [
provider + ':' + prefix2 + ':icon2',
provider + ':' + prefix2 + ':icon4',
],
(loaded, missing, pending, unsubscribe) => { (loaded, missing, pending, unsubscribe) => {
// Callback should be called only once // Callback should be called only once
expect(callbackCalled).to.be.equal(false); expect(callbackCalled).to.be.equal(false);
@ -616,10 +647,12 @@ describe('Testing API loadIcons', () => {
// Test data // Test data
expect(loaded).to.be.eql([ expect(loaded).to.be.eql([
{ {
provider,
prefix: prefix2, prefix: prefix2,
name: 'icon2', name: 'icon2',
}, },
{ {
provider,
prefix: prefix2, prefix: prefix2,
name: 'icon4', name: 'icon4',
}, },

View File

@ -14,6 +14,8 @@ import {
} from './fake_cache'; } from './fake_cache';
describe('Testing mocked localStorage', () => { describe('Testing mocked localStorage', () => {
const provider = '';
it('No usable cache', () => { it('No usable cache', () => {
reset({}); reset({});
@ -85,6 +87,7 @@ describe('Testing mocked localStorage', () => {
cachePrefix + '0', cachePrefix + '0',
JSON.stringify({ JSON.stringify({
cached: Date.now(), cached: Date.now(),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -143,6 +146,7 @@ describe('Testing mocked localStorage', () => {
cachePrefix + '0', cachePrefix + '0',
JSON.stringify({ JSON.stringify({
cached: Date.now(), cached: Date.now(),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {

View File

@ -24,6 +24,8 @@ import {
import { IconifyIcon, IconifyJSON } from '@iconify/types'; import { IconifyIcon, IconifyJSON } from '@iconify/types';
describe('Testing loading from localStorage', () => { describe('Testing loading from localStorage', () => {
const provider = '';
it('Valid icon set', () => { it('Valid icon set', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const cache = createCache(); const cache = createCache();
@ -34,6 +36,7 @@ describe('Testing loading from localStorage', () => {
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -51,7 +54,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Load localStorage // Load localStorage
@ -75,6 +78,64 @@ describe('Testing loading from localStorage', () => {
}); });
}); });
it('Different provider', () => {
const provider = nextPrefix();
const prefix = nextPrefix();
const cache = createCache();
// Add one icon set
cache.setItem(versionKey, cacheVersion);
cache.setItem(countKey, '1');
const item: StoredItem = {
cached: Math.floor(Date.now() / hour),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(cachePrefix + '0', JSON.stringify(item));
// Set cache
reset({
localStorage: cache,
});
// Check icon storage
const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false);
// Check default provider
const icons2 = getStorage('', prefix);
expect(iconExists(icons2, 'foo')).to.be.equal(false);
// Load localStorage
loadCache();
// Icon should exist now
expect(iconExists(icons, 'foo')).to.be.equal(true);
expect(iconExists(icons2, 'foo')).to.be.equal(false);
// Check data
expect(config).to.be.eql({
local: true,
session: false,
});
expect(count).to.be.eql({
local: 1,
session: 0,
});
expect(emptyList).to.be.eql({
local: [],
session: [],
});
});
it('Expired icon set', () => { it('Expired icon set', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const cache = createCache(); const cache = createCache();
@ -86,6 +147,7 @@ describe('Testing loading from localStorage', () => {
const item: StoredItem = { const item: StoredItem = {
// Expiration date // Expiration date
cached: Math.floor(Date.now() / hour) - cacheExpiration - 1, cached: Math.floor(Date.now() / hour) - cacheExpiration - 1,
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -103,7 +165,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Load localStorage // Load localStorage
@ -138,6 +200,7 @@ describe('Testing loading from localStorage', () => {
cachePrefix + '0', cachePrefix + '0',
JSON.stringify({ JSON.stringify({
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -156,7 +219,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Load localStorage // Load localStorage
@ -190,6 +253,7 @@ describe('Testing loading from localStorage', () => {
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -207,7 +271,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Load localStorage // Load localStorage
@ -241,6 +305,7 @@ describe('Testing loading from localStorage', () => {
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -258,7 +323,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Load localStorage // Load localStorage
@ -293,6 +358,7 @@ describe('Testing loading from localStorage', () => {
// Missing: 0, 2, 3 // Missing: 0, 2, 3
const item1: StoredItem = { const item1: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -304,6 +370,7 @@ describe('Testing loading from localStorage', () => {
}; };
const item4: StoredItem = { const item4: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: { data: {
prefix: prefix, prefix: prefix,
icons: { icons: {
@ -323,7 +390,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo1')).to.be.equal(false); expect(iconExists(icons, 'foo1')).to.be.equal(false);
expect(iconExists(icons, 'foo4')).to.be.equal(false); expect(iconExists(icons, 'foo4')).to.be.equal(false);
@ -376,6 +443,7 @@ describe('Testing loading from localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
icons.push(icon); icons.push(icon);
@ -399,7 +467,7 @@ describe('Testing loading from localStorage', () => {
}); });
// Check icon storage // Check icon storage
const iconsStorage = getStorage(prefix); const iconsStorage = getStorage(provider, prefix);
for (let i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal( expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
false, false,

View File

@ -25,6 +25,8 @@ import {
import { IconifyJSON } from '@iconify/types'; import { IconifyJSON } from '@iconify/types';
describe('Testing saving to localStorage', () => { describe('Testing saving to localStorage', () => {
const provider = '';
it('One icon set', () => { it('One icon set', () => {
const prefix = nextPrefix(); const prefix = nextPrefix();
const cache = createCache(); const cache = createCache();
@ -40,6 +42,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
@ -49,11 +52,11 @@ describe('Testing saving to localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
// Save item // Save item
storeCache(icon); storeCache(provider, icon);
// Storing in cache should not add item to storage // Storing in cache should not add item to storage
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
@ -96,6 +99,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item0: StoredItem = { const item0: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon0, data: icon0,
}; };
const icon1: IconifyJSON = { const icon1: IconifyJSON = {
@ -108,6 +112,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item1: StoredItem = { const item1: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon1, data: icon1,
}; };
@ -117,8 +122,8 @@ describe('Testing saving to localStorage', () => {
}); });
// Save items // Save items
storeCache(icon0); storeCache(provider, icon0);
storeCache(icon1); storeCache(provider, icon1);
// Check data that should have been updated because storeCache() // Check data that should have been updated because storeCache()
// should call load function before first execution // should call load function before first execution
@ -161,6 +166,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item0: StoredItem = { const item0: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon0, data: icon0,
}; };
const icon1: IconifyJSON = { const icon1: IconifyJSON = {
@ -173,6 +179,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item1: StoredItem = { const item1: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon1, data: icon1,
}; };
@ -204,7 +211,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Save items // Save items
storeCache(icon0); storeCache(provider, icon0);
// Check data // Check data
expect(count).to.be.eql({ expect(count).to.be.eql({
@ -245,6 +252,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
@ -313,7 +321,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Add item 5 // Add item 5
storeCache(icons[5]); storeCache(provider, icons[5]);
expect(count).to.be.eql({ expect(count).to.be.eql({
local: 0, local: 0,
session: 9, session: 9,
@ -328,7 +336,7 @@ describe('Testing saving to localStorage', () => {
const list = [4, 2, 1]; const list = [4, 2, 1];
list.slice(0).forEach((index) => { list.slice(0).forEach((index) => {
expect(list.shift()).to.be.equal(index); expect(list.shift()).to.be.equal(index);
storeCache(icons[index]); storeCache(provider, icons[index]);
expect(count).to.be.eql({ expect(count).to.be.eql({
local: 0, local: 0,
session: 9, session: 9,
@ -341,7 +349,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Add item 10 // Add item 10
storeCache(icons[10]); storeCache(provider, icons[10]);
expect(count).to.be.eql({ expect(count).to.be.eql({
local: 0, local: 0,
session: 10, session: 10,
@ -353,7 +361,7 @@ describe('Testing saving to localStorage', () => {
expect(cache.getItem(countKey)).to.be.equal('10'); expect(cache.getItem(countKey)).to.be.equal('10');
// Add item 11 // Add item 11
storeCache(icons[11]); storeCache(provider, icons[11]);
expect(count).to.be.eql({ expect(count).to.be.eql({
local: 0, local: 0,
session: 11, session: 11,
@ -392,7 +400,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Check icon storage // Check icon storage
const icons = getStorage(prefix); const icons = getStorage(provider, prefix);
expect(iconExists(icons, 'foo1')).to.be.equal(false); expect(iconExists(icons, 'foo1')).to.be.equal(false);
// Load cache // Load cache
@ -422,11 +430,12 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
// Save item // Save item
storeCache(icon); storeCache(provider, icon);
// Storing in cache should not add item to storage // Storing in cache should not add item to storage
expect(iconExists(icons, 'foo')).to.be.equal(false); expect(iconExists(icons, 'foo')).to.be.equal(false);
@ -473,6 +482,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
cache1.setItem(cachePrefix + index, JSON.stringify(item)); cache1.setItem(cachePrefix + index, JSON.stringify(item));
@ -492,6 +502,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
cache2.setItem(cachePrefix + index, JSON.stringify(item)); cache2.setItem(cachePrefix + index, JSON.stringify(item));
@ -521,7 +532,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Check icon storage // Check icon storage
const iconsStorage = getStorage(prefix); const iconsStorage = getStorage(provider, prefix);
for (let i = 0; i < count.local; i++) { for (let i = 0; i < count.local; i++) {
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal( expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
true, true,
@ -546,9 +557,10 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
storeCache(icon); storeCache(provider, icon);
// Check data // Check data
expect(count).to.be.eql({ expect(count).to.be.eql({
@ -585,6 +597,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
cache1.setItem(cachePrefix + index, JSON.stringify(item)); cache1.setItem(cachePrefix + index, JSON.stringify(item));
@ -604,6 +617,7 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
cache2.setItem(cachePrefix + index, JSON.stringify(item)); cache2.setItem(cachePrefix + index, JSON.stringify(item));
@ -633,7 +647,7 @@ describe('Testing saving to localStorage', () => {
}); });
// Check icon storage // Check icon storage
const iconsStorage = getStorage(prefix); const iconsStorage = getStorage(provider, prefix);
for (let i = 0; i < count.local; i++) { for (let i = 0; i < count.local; i++) {
expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal( expect(iconExists(iconsStorage, 'foo' + i)).to.be.equal(
true, true,
@ -661,9 +675,10 @@ describe('Testing saving to localStorage', () => {
}; };
const item: StoredItem = { const item: StoredItem = {
cached: Math.floor(Date.now() / hour), cached: Math.floor(Date.now() / hour),
provider,
data: icon, data: icon,
}; };
storeCache(icon); storeCache(provider, icon);
// Check data // Check data
expect(count).to.be.eql({ expect(count).to.be.eql({

View File

@ -9,7 +9,7 @@ export function nextPrefix(): string {
} }
// Cache version. Bump when structure changes // Cache version. Bump when structure changes
export const cacheVersion = 'iconify1'; export const cacheVersion = 'iconify2';
// Cache keys // Cache keys
export const cachePrefix = 'iconify'; export const cachePrefix = 'iconify';

View File

@ -19,6 +19,7 @@ import {
getIcon, getIcon,
addIcon, addIcon,
addIconSet, addIconSet,
listStoredProviders,
listStoredPrefixes, listStoredPrefixes,
} from '@iconify/core/lib/storage'; } from '@iconify/core/lib/storage';
import { iconToSVG, IconifyIconBuildResult } from '@iconify/core/lib/builder'; import { iconToSVG, IconifyIconBuildResult } from '@iconify/core/lib/builder';
@ -39,7 +40,7 @@ import { storeCache, loadCache, config } from '@iconify/core/lib/cache/storage';
// API // API
import { API } from '@iconify/core/lib/api/'; import { API } from '@iconify/core/lib/api/';
import { setAPIModule } from '@iconify/core/lib/api/modules'; import { setDefaultAPIModule } from '@iconify/core/lib/api/modules';
import { setAPIConfig, IconifyAPIConfig } from '@iconify/core/lib/api/config'; import { setAPIConfig, IconifyAPIConfig } from '@iconify/core/lib/api/config';
import { prepareQuery, sendQuery } from './modules/api-jsonp'; import { prepareQuery, sendQuery } from './modules/api-jsonp';
import { import {
@ -105,7 +106,7 @@ export interface IconifyGlobal {
/** /**
* List all available icons * List all available icons
*/ */
listIcons: (prefix?: string) => string[]; listIcons: (provider?: string, prefix?: string) => string[];
/** /**
* Load icons * Load icons
@ -157,7 +158,7 @@ export interface IconifyGlobal {
/** /**
* Add icon set to storage * Add icon set to storage
*/ */
addCollection: (data: IconifyJSON) => boolean; addCollection: (data: IconifyJSON, provider?: string) => boolean;
/* API stuff */ /* API stuff */
/** /**
@ -174,8 +175,8 @@ export interface IconifyGlobal {
* Set API configuration * Set API configuration
*/ */
setAPIConfig: ( setAPIConfig: (
customConfig: Partial<IconifyAPIConfig>, provider: string,
prefix?: string | string[] customConfig: Partial<IconifyAPIConfig>
) => void; ) => void;
/* Scan DOM */ /* Scan DOM */
@ -211,7 +212,9 @@ function getIconName(name: string): IconifyIconName | null {
*/ */
function getIconData(name: string): FullIconifyIcon | null { function getIconData(name: string): FullIconifyIcon | null {
const icon = getIconName(name); const icon = getIconName(name);
return icon ? getIcon(getStorage(icon.prefix), icon.name) : null; return icon
? getIcon(getStorage(icon.provider, icon.prefix), icon.name)
: null;
} }
/** /**
@ -282,23 +285,38 @@ const Iconify: IconifyGlobal = {
}, },
// List icons // List icons
listIcons: (prefix?: string) => { listIcons: (provider?: string, prefix?: string) => {
let icons = []; let icons = [];
let prefixes = listStoredPrefixes(); // Get providers
let addPrefix = true; let providers: string[];
if (typeof prefix === 'string') { if (typeof provider === 'string') {
prefixes = prefixes.indexOf(prefix) !== -1 ? [] : [prefix]; providers = [provider];
addPrefix = false; } else {
providers = listStoredProviders();
} }
prefixes.forEach((prefix) => { // Get all icons
const storage = getStorage(prefix); providers.forEach((provider) => {
let icons = Object.keys(storage.icons); let prefixes: string[];
if (addPrefix) {
icons = icons.map((name) => prefix + ':' + name); if (typeof prefix === 'string') {
prefixes = [prefix];
} else {
prefixes = listStoredPrefixes(provider);
} }
icons = icons.concat(icons);
prefixes.forEach((prefix) => {
const storage = getStorage(provider, prefix);
let icons = Object.keys(storage.icons).map(
(name) =>
(provider !== '' ? '@' + provider + ':' : '') +
prefix +
':' +
name
);
icons = icons.concat(icons);
});
}); });
return icons; return icons;
@ -331,16 +349,21 @@ const Iconify: IconifyGlobal = {
if (!icon) { if (!icon) {
return false; return false;
} }
const storage = getStorage(icon.prefix); const storage = getStorage(icon.provider, icon.prefix);
return addIcon(storage, icon.name, data); return addIcon(storage, icon.name, data);
}, },
// Add icon set // Add icon set
addCollection: (data) => { addCollection: (data, provider?: string) => {
if (typeof provider !== 'string') {
provider = '';
}
if ( if (
typeof data !== 'object' || typeof data !== 'object' ||
typeof data.prefix !== 'string' || typeof data.prefix !== 'string' ||
!validateIcon({ !validateIcon({
provider,
prefix: data.prefix, prefix: data.prefix,
name: 'a', name: 'a',
}) })
@ -348,7 +371,7 @@ const Iconify: IconifyGlobal = {
return false; return false;
} }
const storage = getStorage(data.prefix); const storage = getStorage(provider, data.prefix);
return !!addIconSet(storage, data); return !!addIconSet(storage, data);
}, },
@ -404,11 +427,11 @@ coreModules.cache = storeCache;
loadCache(); loadCache();
// Set API // Set API
setAPIModule({ coreModules.api = API;
setDefaultAPIModule({
send: sendQuery, send: sendQuery,
prepare: prepareQuery, prepare: prepareQuery,
}); });
coreModules.api = API;
// Load observer // Load observer
browserModules.observer = observer; browserModules.observer = observer;

View File

@ -84,9 +84,9 @@ function getGlobal(): JSONPRoot {
/** /**
* Calculate maximum icons list length for prefix * Calculate maximum icons list length for prefix
*/ */
function calculateMaxLength(prefix: string): number { function calculateMaxLength(provider: string, prefix: string): number {
// Get config and store path // Get config and store path
const config = getAPIConfig(prefix); const config = getAPIConfig(provider);
if (!config) { if (!config) {
return 0; return 0;
} }
@ -112,7 +112,10 @@ function calculateMaxLength(prefix: string): number {
config.maxURL - config.maxURL -
maxHostLength - maxHostLength -
config.path.length - config.path.length -
endPoint.replace('{prefix}', prefix).replace('{icons}', '').length - endPoint
.replace('{provider}', provider)
.replace('{prefix}', prefix)
.replace('{icons}', '').length -
extraLength; extraLength;
} }
@ -126,6 +129,7 @@ function calculateMaxLength(prefix: string): number {
* Prepare params * Prepare params
*/ */
export const prepareQuery: IconifyAPIPrepareQuery = ( export const prepareQuery: IconifyAPIPrepareQuery = (
provider: string,
prefix: string, prefix: string,
icons: string[] icons: string[]
): APIQueryParams[] => { ): APIQueryParams[] => {
@ -134,11 +138,12 @@ export const prepareQuery: IconifyAPIPrepareQuery = (
// Get maximum icons list length // Get maximum icons list length
let maxLength = maxLengthCache[prefix]; let maxLength = maxLengthCache[prefix];
if (maxLength === void 0) { if (maxLength === void 0) {
maxLength = calculateMaxLength(prefix); maxLength = calculateMaxLength(provider, prefix);
} }
// Split icons // Split icons
let item: APIQueryParams = { let item: APIQueryParams = {
provider,
prefix, prefix,
icons: [], icons: [],
}; };
@ -149,6 +154,7 @@ export const prepareQuery: IconifyAPIPrepareQuery = (
// Next set // Next set
results.push(item); results.push(item);
item = { item = {
provider,
prefix, prefix,
icons: [], icons: [],
}; };
@ -170,6 +176,7 @@ export const sendQuery: IconifyAPISendQuery = (
params: APIQueryParams, params: APIQueryParams,
status: RedundancyPendingItem status: RedundancyPendingItem
): void => { ): void => {
const provider = params.provider;
const prefix = params.prefix; const prefix = params.prefix;
const icons = params.icons; const icons = params.icons;
const iconsList = icons.join(','); const iconsList = icons.join(',');
@ -180,7 +187,9 @@ export const sendQuery: IconifyAPISendQuery = (
const global = getGlobal(); const global = getGlobal();
// Callback hash // Callback hash
let cbCounter = hash(host + ':' + prefix + ':' + iconsList); let cbCounter = hash(
provider + ':' + host + ':' + prefix + ':' + iconsList
);
while (global[cbPrefix + cbCounter] !== void 0) { while (global[cbPrefix + cbCounter] !== void 0) {
cbCounter++; cbCounter++;
} }
@ -189,6 +198,7 @@ export const sendQuery: IconifyAPISendQuery = (
let path = let path =
pathCache[prefix] + pathCache[prefix] +
endPoint endPoint
.replace('{provider}', provider)
.replace('{prefix}', prefix) .replace('{prefix}', prefix)
.replace('{icons}', iconsList) .replace('{icons}', iconsList)
.replace('{cb}', callbackName); .replace('{cb}', callbackName);

View File

@ -53,18 +53,20 @@ export function scanDOM(root?: HTMLElement): void {
// Observer // Observer
let paused = false; let paused = false;
// List of icons to load // List of icons to load: [provider][prefix][name] = boolean
const loadIcons: Record<string, Record<string, boolean>> = Object.create( const loadIcons: Record<
null string,
); Record<string, Record<string, boolean>>
> = Object.create(null);
// Get root node and placeholders // Get root node and placeholders
if (!root) { if (!root) {
root = getRoot(); root = getRoot();
} }
findPlaceholders(root).forEach(item => { findPlaceholders(root).forEach((item) => {
const element = item.element; const element = item.element;
const iconName = item.name; const iconName = item.name;
const provider = iconName.provider;
const prefix = iconName.prefix; const prefix = iconName.prefix;
const name = iconName.name; const name = iconName.name;
let data: IconifyElementData = element[elementDataProperty]; let data: IconifyElementData = element[elementDataProperty];
@ -79,7 +81,7 @@ export function scanDOM(root?: HTMLElement): void {
case 'loading': case 'loading':
if ( if (
coreModules.api && coreModules.api &&
coreModules.api.isPending(prefix, name) coreModules.api.isPending({ provider, prefix, name })
) { ) {
// Pending // Pending
return; return;
@ -88,7 +90,7 @@ export function scanDOM(root?: HTMLElement): void {
} }
// Check icon // Check icon
const storage = getStorage(prefix); const storage = getStorage(provider, prefix);
if (storage.icons[name] !== void 0) { if (storage.icons[name] !== void 0) {
// Icon exists - replace placeholder // Icon exists - replace placeholder
if (browserModules.observer && !paused) { if (browserModules.observer && !paused) {
@ -124,12 +126,16 @@ export function scanDOM(root?: HTMLElement): void {
} }
if (coreModules.api) { if (coreModules.api) {
if (!coreModules.api.isPending(prefix, name)) { if (!coreModules.api.isPending({ provider, prefix, name })) {
// Add icon to loading queue // Add icon to loading queue
if (loadIcons[prefix] === void 0) { if (loadIcons[provider] === void 0) {
loadIcons[prefix] = Object.create(null); loadIcons[provider] = Object.create(null);
} }
loadIcons[prefix][name] = true; const providerLoadIcons = loadIcons[provider];
if (providerLoadIcons[prefix] === void 0) {
providerLoadIcons[prefix] = Object.create(null);
}
providerLoadIcons[prefix][name] = true;
} }
} }
@ -145,17 +151,21 @@ export function scanDOM(root?: HTMLElement): void {
// Load icons // Load icons
if (coreModules.api) { if (coreModules.api) {
const api = coreModules.api; const api = coreModules.api;
Object.keys(loadIcons).forEach(prefix => { Object.keys(loadIcons).forEach((provider) => {
api.loadIcons( const providerLoadIcons = loadIcons[provider];
Object.keys(loadIcons[prefix]).map(name => { Object.keys(providerLoadIcons).forEach((prefix) => {
const icon: IconifyIconName = { api.loadIcons(
prefix, Object.keys(providerLoadIcons[prefix]).map((name) => {
name, const icon: IconifyIconName = {
}; provider,
return icon; prefix,
}), name,
checkPendingIcons };
); return icon;
}),
checkPendingIcons
);
});
}); });
} }