2
0
mirror of https://github.com/iconify/iconify.git synced 2024-11-09 23:00:56 +00:00

Change behavior of scanDOM in SVG framework when used with custom root node

This commit is contained in:
Vjacheslav Trushkin 2020-08-02 21:48:53 +03:00
parent 4207fd254a
commit d1bd40bcb0
7 changed files with 372 additions and 96 deletions

View File

@ -6,7 +6,7 @@ import { addFinder } from '@iconify/iconify/lib/modules/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { getStorage, addIconSet } from '@iconify/core/lib/storage';
import { setRoot } from '@iconify/iconify/lib/modules/root';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner';
const expect = chai.expect;
@ -42,6 +42,9 @@ describe('Scanning DOM', () => {
height: 24,
});
// Sanity check before running tests
expect(getRootNodes()).to.be.eql([]);
it('Scan DOM with preloaded icons', () => {
const node = getNode('scan-dom');
node.innerHTML =
@ -56,29 +59,46 @@ describe('Scanning DOM', () => {
'</li>' +
'</ul></div>';
// Scan node
setRoot(node);
scanDOM();
// Find elements
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(4);
// Make sure tempoary node was not added as root, but new root node was
expect(getRootNodes()).to.be.eql([
{
node: node,
temporary: false,
},
]);
});
it('Scan DOM with unattached root', () => {
const node = document.createElement('div');
node.innerHTML = '<span class="iconify" data-icon="mdi:home"></span>';
// Get old root nodes. It should not be empty because of previous test(s)
const oldRoot = getRootNodes();
// Scan node
scanDOM(node);
// Find elements
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
// Make sure tempoary node was not added as root
expect(getRootNodes()).to.be.eql(oldRoot);
});
it('Scan DOM with icon as root', () => {
const node = document.createElement('span');
node.setAttribute('data-icon', 'mdi:home');
// Scan node
scanDOM(node);
// Check node

View File

@ -0,0 +1,117 @@
import mocha from 'mocha';
import chai from 'chai';
import { getNode } from './node';
import { addFinder } from '@iconify/iconify/lib/modules/finder';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { getStorage, addIconSet } from '@iconify/core/lib/storage';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner';
import { initObserver } from '@iconify/iconify/lib/modules/observer';
const expect = chai.expect;
// Add finders
addFinder(iconifyFinder);
addFinder(iconifyIconFinder);
describe('Observe DOM', () => {
// Add mentioned icons to storage
const storage = getStorage('', 'mdi');
addIconSet(storage, {
prefix: 'mdi',
icons: {
'account-box': {
body:
'<path d="M6 17c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6m9-9a3 3 0 0 1-3 3a3 3 0 0 1-3-3a3 3 0 0 1 3-3a3 3 0 0 1 3 3M3 5v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z" fill="currentColor"/>',
},
'account-cash': {
body:
'<path d="M11 8c0 2.21-1.79 4-4 4s-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4m0 6.72V20H0v-2c0-2.21 3.13-4 7-4c1.5 0 2.87.27 4 .72M24 20H13V3h11v17m-8-8.5a2.5 2.5 0 0 1 5 0a2.5 2.5 0 0 1-5 0M22 7a2 2 0 0 1-2-2h-3c0 1.11-.89 2-2 2v9a2 2 0 0 1 2 2h3c0-1.1.9-2 2-2V7z" fill="currentColor"/>',
},
'account': {
body:
'<path d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4z" fill="currentColor"/>',
},
'home': {
body:
'<path d="M10 20v-6h4v6h5v-8h3L12 3L2 12h3v8h5z" fill="currentColor"/>',
},
},
width: 24,
height: 24,
});
it('Basic test', (done) => {
const node = getNode('observe-dom');
// Set root and init observer
setRoot(node);
initObserver(scanDOM);
// Test getRootNodes
expect(getRootNodes()).to.be.eql([
{
node: node,
temporary: false,
},
]);
// Set HTML
node.innerHTML =
'<p>Testing observing DOM</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
// Test nodes
setTimeout(() => {
// Find elements
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
// Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
done();
}, 100);
});
it('Change icon', (done) => {
const node = getNode('observe-dom');
// Set root and init observer
setRoot(node);
initObserver(scanDOM);
// Set HTML
node.innerHTML =
'<p>Testing observing DOM</p>' +
'<span class="iconify" data-icon="mdi:home"></span>';
// Test nodes
setTimeout(() => {
// Find elements
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
// Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.not.be.equal(-1);
// Change icon
elements[0].setAttribute('data-icon', 'mdi:account');
// Test nodes after timer
setTimeout(() => {
// Find elements
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(1);
// Test for "home" icon contents
expect(node.innerHTML.indexOf('20v-6h4v6h5v')).to.be.equal(-1);
expect(node.innerHTML.indexOf('M12 4a4')).to.not.be.equal(-1);
done();
}, 100);
}, 100);
});
});

View File

@ -10,7 +10,7 @@ import { setAPIConfig } from '@iconify/core/lib/api/config';
import { coreModules } from '@iconify/core/lib/modules';
import { finder as iconifyFinder } from '@iconify/iconify/lib/finders/iconify';
import { finder as iconifyIconFinder } from '@iconify/iconify/lib/finders/iconify-icon';
import { setRoot } from '@iconify/iconify/lib/modules/root';
import { setRoot, getRootNodes } from '@iconify/iconify/lib/modules/root';
import { scanDOM } from '@iconify/iconify/lib/modules/scanner';
const expect = chai.expect;
@ -114,10 +114,18 @@ describe('Scanning DOM with API', () => {
'</li>' +
'</ul></div>';
// Scan DOM
setRoot(node);
scanDOM();
// Test getRootNodes
expect(getRootNodes()).to.be.eql([
{
node: node,
temporary: false,
},
]);
// First API response should have loaded
setTimeout(() => {
const elements = node.querySelectorAll('svg.iconify');
@ -241,10 +249,18 @@ describe('Scanning DOM with API', () => {
'</li>' +
'</ul></div>';
// Scan DOM
setRoot(node);
scanDOM();
// Test getRootNodes
expect(getRootNodes()).to.be.eql([
{
node: node,
temporary: false,
},
]);
// Make sure no icons were rendered yet
const elements = node.querySelectorAll('svg.iconify');
expect(elements.length).to.be.equal(
@ -369,8 +385,8 @@ describe('Scanning DOM with API', () => {
'</li>' +
'</ul></div>';
// Scan DOM
setRoot(node);
scanDOM();
// Change icon name
@ -429,18 +445,47 @@ describe('Scanning DOM with API', () => {
prefix +
':home"></span>';
console.log('\nUnattached node test start');
// Set root node, test nodes list
setRoot(fakeRoot);
expect(getRootNodes()).to.be.eql([
{
node: fakeRoot,
temporary: false,
},
]);
// Scan different node
scanDOM(node);
// Test nodes list
expect(getRootNodes()).to.be.eql([
{
node: fakeRoot,
temporary: false,
},
{
node: node,
temporary: true,
},
]);
// API response should have loaded
setTimeout(() => {
const elements = node.querySelectorAll('svg.iconify');
console.log('Unattached node test:', node.innerHTML);
expect(elements.length).to.be.equal(
1,
'Expected to find 1 rendered SVG element'
);
// Test nodes list: temporary node should have been removed
expect(getRootNodes()).to.be.eql([
{
node: fakeRoot,
temporary: false,
},
]);
// Done
done();
}, 200);
});

View File

@ -66,7 +66,6 @@ describe('Testing Iconify object', () => {
expect(node).to.not.be.equal(null);
const html = node.outerHTML;
console.log('Rendered SVG:', html);
expect(html.indexOf('<svg')).to.be.equal(0);
// Get HTML

View File

@ -66,7 +66,6 @@ describe('Testing Iconify object (without API)', () => {
expect(node).to.not.be.equal(null);
const html = node.outerHTML;
console.log('Rendered SVG:', html);
expect(html.indexOf('<svg')).to.be.equal(0);
// Get HTML

View File

@ -1,6 +1,15 @@
// Root element
// Default root element
let root: HTMLElement;
// Interface for extra root nodes
export interface ExtraRootNode {
node: HTMLElement;
temporary: boolean; // True if node should be removed when all placeholders have been replaced
}
// Additional root elements
let customRoot: ExtraRootNode[] = [];
/**
* Get root element
*/
@ -14,3 +23,58 @@ export function getRoot(): HTMLElement {
export function setRoot(node: HTMLElement): void {
root = node;
}
/**
* Add extra root node
*/
export function addRoot(node: HTMLElement, autoRemove = false): ExtraRootNode {
if (root === node) {
return {
node: root,
temporary: false,
};
}
for (let i = 0; i < customRoot.length; i++) {
const item = customRoot[i];
if (item.node === node) {
// Found matching node
if (!autoRemove && item.temporary) {
// Change to permanent
item.temporary = false;
}
return item;
}
}
// Add item
const item = {
node,
temporary: autoRemove,
};
customRoot.push(item);
return item;
}
/**
* Remove root node
*/
export function removeRoot(node: HTMLElement): void {
customRoot = customRoot.filter((item) => item.node !== node);
}
/**
* Get all root nodes
*/
export function getRootNodes(): ExtraRootNode[] {
return (root
? [
{
node: root,
temporary: false,
},
]
: []
).concat(customRoot);
}

View File

@ -6,7 +6,13 @@ import { findPlaceholders } from './finder';
import { IconifyElementData, elementDataProperty } from './element';
import { renderIcon } from './render';
import { pauseObserver, resumeObserver } from './observer';
import { getRoot } from './root';
import {
getRoot,
getRootNodes,
addRoot,
removeRoot,
ExtraRootNode,
} from './root';
/**
* Flag to avoid scanning DOM too often
@ -48,25 +54,39 @@ const compareIcons = (
/**
* Scan DOM for placeholders
*/
export function scanDOM(root?: HTMLElement): void {
export function scanDOM(customRoot?: HTMLElement): void {
scanQueued = false;
// Observer
let paused = false;
// List of icons to load: [provider][prefix][name] = boolean
const loadIcons: Record<
string,
Record<string, Record<string, boolean>>
> = Object.create(null);
// Get root node and placeholders
if (!root) {
root = getRoot();
// Add temporary root node
let customRootItem: ExtraRootNode;
if (customRoot) {
customRootItem = addRoot(customRoot, true);
}
// Get root node and placeholders
const rootNodes: ExtraRootNode[] = customRoot
? [customRootItem]
: getRootNodes();
rootNodes.forEach((rootItem) => {
const root = rootItem.node;
if (!root || !root.querySelectorAll) {
return;
}
// Track placeholders
let hasPlaceholders = false;
// Observer
let paused = false;
// Find placeholders
findPlaceholders(root).forEach((item) => {
const element = item.element;
const iconName = item.name;
@ -85,9 +105,14 @@ export function scanDOM(root?: HTMLElement): void {
case 'loading':
if (
coreModules.api &&
coreModules.api.isPending({ provider, prefix, name })
coreModules.api.isPending({
provider,
prefix,
name,
})
) {
// Pending
hasPlaceholders = true;
return;
}
}
@ -97,7 +122,7 @@ export function scanDOM(root?: HTMLElement): void {
const storage = getStorage(provider, prefix);
if (storage.icons[name] !== void 0) {
// Icon exists - replace placeholder
if (!paused) {
if (!paused && !rootItem.temporary) {
pauseObserver(root);
paused = true;
}
@ -150,6 +175,17 @@ export function scanDOM(root?: HTMLElement): void {
customisations: {},
};
element[elementDataProperty] = data;
hasPlaceholders = true;
});
// Remove temporay node
if (rootItem.temporary && !hasPlaceholders) {
removeRoot(root);
}
if (paused && !rootItem.temporary) {
resumeObserver(root);
}
});
// Load icons
@ -172,8 +208,4 @@ export function scanDOM(root?: HTMLElement): void {
});
});
}
if (paused) {
resumeObserver(root);
}
}