xl-infinite-tree
Version:
A browser-ready tree library that can efficiently display a large amount of data using infinite scrolling.
1,311 lines (1,116 loc) • 41.5 kB
JavaScript
import { test } from 'tap';
import fs from 'fs';
import path from 'path';
import { JSDOM, VirtualConsole } from 'jsdom';
import { Node } from 'flattree';
const virtualConsole = new VirtualConsole();
virtualConsole.sendTo(console);
const dom = new JSDOM('', { virtualConsole });
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
const getTreeData = () => {
const json = fs.readFileSync(path.resolve('test/fixtures/tree.json'), 'utf8');
return Object.assign({}, JSON.parse(json));
};
const InfiniteTree = require('../src');
const getTreeElement = () => {
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
const el = document.createElement('div');
el.setAttribute('id', 'tree');
document.body.appendChild(el);
return el;
};
test('Stealth mode', (t) => {
{ // #1
const tree = new InfiniteTree(null, {
autoOpen: true,
data: getTreeData()
});
t.equal(tree.clusterize, null);
t.equal(tree.contentElement, null);
t.equal(tree.scrollElement, null);
t.equal(tree.draggableTarget, null);
t.equal(tree.droppableTarget, null);
t.equal(tree.nodes.length, 12);
t.equal(tree.rows.length, 12);
tree.destroy();
t.equal(tree.clusterize, null);
t.equal(tree.contentElement, null);
t.equal(tree.scrollElement, null);
t.equal(tree.draggableTarget, null);
t.equal(tree.droppableTarget, null);
t.equal(tree.nodes.length, 0);
t.equal(tree.rows.length, 0);
}
{ // #2
const tree = new InfiniteTree({
autoOpen: true,
data: getTreeData()
});
t.equal(tree.clusterize, null);
t.equal(tree.contentElement, null);
t.equal(tree.scrollElement, null);
t.equal(tree.draggableTarget, null);
t.equal(tree.droppableTarget, null);
t.equal(tree.nodes.length, 12);
t.equal(tree.rows.length, 12);
tree.destroy();
t.equal(tree.clusterize, null);
t.equal(tree.contentElement, null);
t.equal(tree.scrollElement, null);
t.equal(tree.draggableTarget, null);
t.equal(tree.droppableTarget, null);
t.equal(tree.nodes.length, 0);
t.equal(tree.rows.length, 0);
}
t.end();
});
test('Close all nodes on tree initialization', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
t.strictSame(tree.nodes.length, 1);
t.strictSame(tree.rows.length, 1);
t.end();
});
test('Open all nodes on tree initialization', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
t.strictSame(tree.nodes.length, 12);
t.strictSame(tree.rows.length, 12);
t.end();
});
test('It should generate expected output for empty result', (t) => {
const el = getTreeElement();
const options = {
noDataClass: 'my-no-data-class',
noDataText: 'My no data text'
};
const tree = new InfiniteTree(el, options);
const innerHTML = `<div id="tree"><div class="infinite-tree infinite-tree-scroll"><div class="infinite-tree infinite-tree-content" tabindex="0"><div class="${options.noDataClass}">${options.noDataText}</div></div></div></div>`;
t.strictSame(window.document.body.innerHTML, innerHTML);
t.strictSame(tree.nodes.length, 0);
t.strictSame(tree.rows.length, 0);
t.end();
});
test('loadOnDemand', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: {
id: '<root>',
loadOnDemand: true
},
loadNodes: (node, next) => {
t.equal(tree.getNodeById('<root>').getChildren().length, 0);
// Asynchronous
setTimeout(() => {
next(null, [], () => {
t.equal(tree.getNodeById('<root>').getChildren().length, 0);
t.equal(tree.state.openNodes.length, 1);
t.equal(node.state.open, true);
t.end();
});
}, 250);
// Asynchronous
setTimeout(() => {
const data = getTreeData();
next(null, data.children, () => {
t.equal(tree.getNodeById('<root>').getChildren().length, 2);
t.equal(tree.state.openNodes.length, 1);
t.end();
});
}, 500);
}
});
t.ok(tree.openNode(tree.getNodeById('<root>')));
});
test('shouldLoadNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: {
id: '<root>',
loadNodes: true
},
shouldLoadNodes: (node) => {
return !node.hasChildren() && node.loadNodes;
},
loadNodes: (node, next) => {
t.equal(tree.getNodeById('<root>').getChildren().length, 0);
// Asynchronous
setTimeout(() => {
next(null, [], () => {
t.equal(tree.getNodeById('<root>').getChildren().length, 0);
t.equal(tree.state.openNodes.length, 1);
t.equal(node.state.open, true);
t.end();
});
}, 250);
// Asynchronous
setTimeout(() => {
const data = getTreeData();
next(null, data.children, () => {
t.equal(tree.getNodeById('<root>').getChildren().length, 2);
t.equal(tree.state.openNodes.length, 1);
t.end();
});
}, 500);
}
});
t.ok(tree.openNode(tree.getNodeById('<root>')));
});
test('tree.destroy', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
t.notEqual(tree.clusterize, null);
t.notEqual(tree.contentElement, null);
t.notEqual(tree.scrollElement, null);
t.equal(tree.nodes.length, 12);
t.equal(tree.rows.length, 12);
tree.destroy();
t.equal(tree.clusterize, null);
t.equal(tree.contentElement, null);
t.equal(tree.scrollElement, null);
t.equal(tree.nodes.length, 0);
t.equal(tree.rows.length, 0);
t.end();
});
test('tree.addChildNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
const nodesLength = tree.nodes.length;
{ // #1: Add a child node to the root node without specifying index
tree.addChildNodes({ id: 'new-node#1' });
t.equal(tree.nodes.length, nodesLength + 1);
t.notEqual(tree.getNodeById('new-node#1'), null);
t.strictSame(tree.getNodeById('new-node#1').getPreviousSibling(), tree.getNodeById('<root>'));
t.equal(tree.getNodeById('new-node#1').getNextSibling(), null);
}
{ // #2: Add a child node to the root node at the specified index
tree.addChildNodes({ id: 'new-node#2' }, 1);
t.equal(tree.nodes.length, nodesLength + 2);
t.notEqual(tree.getNodeById('new-node#2'), null);
t.strictSame(tree.getNodeById('new-node#2').getPreviousSibling(), tree.getNodeById('<root>'));
t.strictSame(tree.getNodeById('new-node#2').getNextSibling(), tree.getNodeById('new-node#1'));
}
t.end();
});
test('tree.appendChildNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
const nodesLength = tree.nodes.length;
{ // #1: Append a child node to the root node
tree.appendChildNode({ id: 'new-node#1' });
t.equal(tree.nodes.length, nodesLength + 1);
t.notEqual(tree.getNodeById('new-node#1'), null);
t.strictSame(tree.getNodeById('new-node#1').getPreviousSibling(), tree.getNodeById('<root>'));
t.equal(tree.getNodeById('new-node#1').getNextSibling(), null);
}
t.end();
});
test('tree.clear', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
t.equal(tree.nodes.length, 12);
tree.clear();
t.equal(tree.nodes.length, 0);
t.end();
});
test('tree.closeNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
let eventFiredCount = 0;
tree.on('closeNode', (node) => {
++eventFiredCount;
});
// Close Node
t.notOk(tree.closeNode());
t.equal(tree.nodes.length, 12);
tree.closeNode(tree.getNodeById('india'));
t.equal(tree.nodes.length, 11);
tree.closeNode(tree.getNodeById('delta'));
t.equal(tree.nodes.length, 9);
tree.closeNode(tree.getNodeById('hotel'));
t.equal(tree.nodes.length, 8);
tree.closeNode(tree.getNodeById('charlie'));
t.equal(tree.nodes.length, 6);
tree.closeNode(tree.getNodeById('bravo'));
t.equal(tree.nodes.length, 3);
tree.closeNode(tree.getNodeById('<root>'), { silent: true }); // Prevent event from being triggered
t.equal(tree.nodes.length, 1);
// Check event fired count
t.equal(eventFiredCount, 5);
t.end();
});
test('tree.filter', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
t.equal(tree.filtered, false);
{ // No predicate
tree.filter();
t.equal(tree.filtered, true);
const nodes = tree.flattenChildNodes();
t.equal(nodes.filter(node => node.state.filtered === true).length, 0);
t.equal(nodes.filter(node => node.state.filtered === false).length, 12);
t.equal(tree.rows.filter(row => !!row).length, 0);
}
{ // Invalid predicate: Object
tree.filter({});
t.equal(tree.filtered, true);
const nodes = tree.flattenChildNodes();
t.equal(nodes.filter(node => node.state.filtered === true).length, 0);
t.equal(nodes.filter(node => node.state.filtered === false).length, 12);
t.equal(tree.rows.filter(row => !!row).length, 0);
}
{ // Invalid predicate: Number
tree.filter(0);
t.equal(tree.filtered, true);
const nodes = tree.flattenChildNodes();
t.equal(nodes.filter(node => node.state.filtered === true).length, 0);
t.equal(nodes.filter(node => node.state.filtered === false).length, 12);
t.equal(tree.rows.filter(row => !!row).length, 0);
}
{ // Empty keyword
tree.filter('');
t.equal(tree.filtered, true);
const nodes = tree.flattenChildNodes();
t.equal(nodes.filter(node => node.state.filtered === true).length, 12);
t.equal(nodes.filter(node => node.state.filtered === false).length, 0);
t.equal(tree.rows.filter(row => !!row).length, 12);
}
{ // Not matched
tree.filter('none');
t.equal(tree.filtered, true);
const nodes = tree.flattenChildNodes();
t.equal(nodes.filter(node => node.state.filtered === true).length, 0);
t.equal(nodes.filter(node => node.state.filtered === false).length, 12);
t.equal(tree.rows.filter(row => !!row).length, 0);
}
const testCases = [
{ // Empty predicate
predicate: null,
options: {
caseSensitive: false,
exactMatch: false,
includeAncestors: true,
includeDescendants: true
},
wanted: []
},
{ // Invalid filterPath
predicate: 'charlie',
options: {
filterPath: 'children'
},
wanted: []
},
{ // Invalid filterPath
predicate: 'charlie',
options: {
filterPath: 'state.open'
},
wanted: []
},
{ // Case sensitive
predicate: 'charlie',
options: {
caseSensitive: true,
exactMatch: false,
includeAncestors: false,
includeDescendants: false
},
wanted: []
},
{ // Case sensitive
predicate: 'Charlie',
options: {
caseSensitive: true,
exactMatch: false,
includeAncestors: false,
includeDescendants: false
},
wanted: ['Charlie']
},
{ // Exact match
predicate: 'Charlie ',
options: {
caseSensitive: true,
exactMatch: true,
includeAncestors: false,
includeDescendants: false
},
wanted: []
},
{ // Exact match
predicate: 'Charlie',
options: {
caseSensitive: true,
exactMatch: true,
includeAncestors: false,
includeDescendants: false
},
wanted: ['Charlie']
},
{ // Include ancestors
predicate: 'charlie',
options: {
caseSensitive: false,
exactMatch: false,
includeAncestors: true,
includeDescendants: false
},
wanted: ['<root>', 'Bravo', 'Charlie']
},
{ // Include descendants
predicate: 'charlie',
options: {
caseSensitive: false,
exactMatch: false,
includeAncestors: false,
includeDescendants: true
},
wanted: ['Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf']
},
{ // Include ancestors and descendants
predicate: 'charlie',
options: {
caseSensitive: false,
exactMatch: false,
includeAncestors: true,
includeDescendants: true
},
wanted: ['<root>', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf']
},
{ // No ancestors and descendants
predicate: 'charlie',
options: {
caseSensitive: false,
exactMatch: false,
includeAncestors: false,
includeDescendants: false
},
wanted: ['Charlie']
},
{ // Function
predicate: function(node) {
return node.label === 'Charlie';
},
options: {
includeAncestors: false,
includeDescendants: false
},
wanted: ['Charlie']
},
{ // Function
predicate: function(node) {
return node.label === 'Charlie';
},
options: {
includeAncestors: true,
includeDescendants: false
},
wanted: ['<root>', 'Bravo', 'Charlie']
},
{ // Function
predicate: function(node) {
return node.label === 'Charlie';
},
options: {
includeAncestors: false,
includeDescendants: true
},
wanted: ['Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf']
},
{ // Function
predicate: function(node) {
return node.label === 'Charlie';
},
options: {
includeAncestors: true,
includeDescendants: true
},
wanted: ['<root>', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf']
}
];
for (let i = 0; i < testCases.length; ++i) {
const testCase = testCases[i];
const { predicate, options } = testCase;
if (typeof predicate === 'string') {
options.filterPath = options.filterPath || 'label';
}
tree.filter(predicate, options);
const found = tree.flattenChildNodes()
.filter(node => node.state.filtered)
.map(node => node.label);
t.strictSame(found, testCase.wanted);
}
t.end();
});
test('tree.flattenChildNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
{ // #1: Flatten an non-Node object
const nodes = tree.flattenChildNodes({});
t.equal(nodes.length, 0);
}
{ // #2: Flatten all nodes within the tree
const nodes = tree.flattenChildNodes();
const wanted = tree.nodes;
t.strictSame(nodes, wanted);
}
{ // #3: Flatten all child nodes of a node
const node = tree.getNodeById('<root>');
const nodes = tree.flattenChildNodes(node);
const wanted = tree.nodes.slice(tree.nodes.indexOf(node) + 1);
t.strictSame(nodes, wanted);
}
t.end();
});
test('tree.flattenNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
{ // #1: Pass null as a parameter
const nodes = tree.flattenNode(null);
const wanted = [];
t.strictSame(nodes, wanted);
}
{ // #2: Pass empty parameters
const nodes = tree.flattenNode();
const wanted = [];
t.strictSame(nodes, wanted);
}
{ // #3: Flatten a node
const node = tree.getNodeById('bravo');
const nodes = tree.flattenNode(node);
const wanted = tree.nodes.slice(tree.nodes.indexOf(node) + 0);
t.strictSame(nodes, wanted);
}
t.end();
});
test('tree.getChildNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
{ // #1: Flatten an non-Node object
const nodes = tree.getChildNodes({});
t.equal(nodes.length, 0);
}
{ // #2: Get child nodes of the root node
const nodes = tree.getChildNodes().map((node) => {
return {
id: node.id,
label: node.label,
children: node.children,
state: node.state
};
});
const wanted = [
tree.getNodeById('<root>')
].map((node) => {
return {
id: node.id,
label: node.label,
children: node.children,
state: node.state
};
});
t.strictSame(nodes, wanted);
}
{ // #3: Get child nodes of a node
const node = tree.getNodeById('bravo');
const nodes = tree.getChildNodes(node).map((node) => {
return {
id: node.id,
label: node.label,
children: node.children,
state: node.state
};
});
const wanted = [
tree.getNodeById('charlie'),
tree.getNodeById('hotel'),
tree.getNodeById('kilo')
].map((node) => {
return {
id: node.id,
label: node.label,
children: node.children,
state: node.state
};
});
t.strictSame(nodes, wanted);
}
t.end();
});
test('tree.getNodeById', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
t.notEqual(tree.getNodeById('<root>').id, null);
t.equal(tree.getNodeById('none'), null);
// Make sure it will rebuild the nodeTable if a node does not exist
tree.nodeTable.clear();
t.equal(tree.nodeTable.get('<root>'), undefined);
t.notEqual(tree.getNodeById('<root>'), null);
t.strictSame(tree.nodeTable.get('<root>'), tree.getNodeById('<root>'));
t.end();
});
test('tree.getNodeFromPoint', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
//t.notOk(tree.getNodeFromPoint(0, 0));
t.end();
});
test('tree.getOpenNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
})
const found = tree.getOpenNodes().map(node => node.id);
const wanted = ['<root>', 'bravo', 'charlie', 'delta', 'hotel', 'india'];
t.strictSame(found, wanted);
t.end();
});
test('tree.getRootNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
const node = tree.getRootNode();
const found = {
id: node.id,
parent: node.parent,
state: node.state
};
const wanted = {
id: null,
parent: null,
state: {
depth: -1,
open: true,
path: '',
prefixMask: '',
total: 1
}
};
t.strictSame(found, wanted);
t.end();
});
test('tree.getSelectedNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
const node = tree.getNodeById('<root>');
t.ok(tree.selectNode(node));
t.strictSame(tree.getSelectedNode(), node);
t.end();
});
test('tree.insertNodeAfter', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
const notNode = {};
tree.insertNodeAfter({ id: 'new-node' }, notNode);
t.equal(tree.getNodeById('new-node'), null);
tree.insertNodeAfter({ id: 'new-node' }, tree.getNodeById('<root>'));
t.strictSame(tree.getNodeById('new-node'), tree.getNodeById('<root>').getNextSibling());
t.end();
});
test('tree.insertNodeBefore', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
data: getTreeData()
});
const notNode = {};
tree.insertNodeBefore({ id: 'new-node' }, notNode);
t.equal(tree.getNodeById('new-node'), null);
tree.insertNodeBefore({ id: 'new-node' }, tree.getNodeById('<root>'));
t.strictSame(tree.getNodeById('new-node'), tree.getNodeById('<root>').getPreviousSibling());
t.end();
});
test('tree.openNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
let eventFiredCount = 0;
tree.on('openNode', (node) => {
++eventFiredCount;
});
// Not a Node object
t.notOk(tree.openNode());
// Not an existing Node object
t.notOk(tree.openNode(new Node()));
// The first node is `Node { id: "<root>" }`
t.strictSame(tree.nodes[0], tree.getNodeById('<root>'));
// Should be no open nodes at initial
t.equal(tree.state.openNodes.length, 0);
// Pass `{ silent: true }` to prevent event from being triggered
t.ok(tree.openNode(tree.getNodeById('<root>'), { silent: true }));
t.equal(tree.nodes.length, 3);
t.equal(tree.state.openNodes.length, 1);
t.equal(tree.openNode(tree.getNodeById('<root>')), false, 'it should return false when trying to re-open a node');
t.ok(tree.openNode(tree.getNodeById('bravo')));
t.equal(tree.nodes.length, 6);
t.equal(tree.state.openNodes.length, 2);
t.ok(tree.openNode(tree.getNodeById('charlie')));
t.equal(tree.nodes.length, 8);
t.equal(tree.state.openNodes.length, 3);
t.ok(tree.openNode(tree.getNodeById('hotel')));
t.equal(tree.nodes.length, 9);
t.equal(tree.state.openNodes.length, 4);
t.ok(tree.openNode(tree.getNodeById('delta')));
t.equal(tree.nodes.length, 11);
t.equal(tree.state.openNodes.length, 5);
t.ok(tree.openNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, 12);
t.equal(tree.state.openNodes.length, 6);
tree.closeNode(tree.getNodeById('hotel'));
t.equal(tree.nodes.length, 10);
t.equal(tree.state.openNodes.length, 5);
t.notOk(tree.closeNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, 10);
t.equal(tree.state.openNodes.length, 5);
// Check event fired count
t.equal(eventFiredCount, 5);
// Should be able to open existed hidden node (happening async loaded children appended to a
// node, which was collapsed till data was fully loaded).
t.notOk(tree.openNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, 10);
t.equal(tree.state.openNodes.length, 5);
// Check event fired count
t.equal(eventFiredCount, 5);
// Open the node again should not change the result of tree.state.openNodes
t.notOk(tree.openNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, 10);
t.equal(tree.state.openNodes.length, 5);
// Check event fired count
t.equal(eventFiredCount, 5); // should not fire event
t.end();
});
test('tree.moveNodeTo', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
{ // Move "alpha" to "bravo"
const root = tree.getNodeById('<root>');
const alpha = tree.getNodeById('alpha');
const bravo = tree.getNodeById('bravo');
t.equal(root.children.indexOf(alpha), 0);
t.equal(root.children.indexOf(bravo), 1);
t.ok(tree.moveNodeTo(alpha, bravo));
t.equal(root.children.indexOf(alpha), -1);
t.equal(root.children.indexOf(bravo), 0);
t.equal(bravo.children.indexOf(alpha), 3);
}
{ // Move "bravo" to "root"
const root = tree.getNodeById('<root>');
const bravo = tree.getNodeById('bravo');
t.equal(bravo.parent, root);
t.notOk(tree.moveNodeTo(root, bravo));
t.equal(bravo.parent, root);
}
t.end();
});
test('tree.removeChildNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
const nodesLength = tree.nodes.length;
// Select a node
t.ok(tree.selectNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, nodesLength);
{ // #1: Pass empty parameters
t.notOk(tree.removeChildNodes());
t.equal(tree.nodes.length, nodesLength);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo',
'charlie',
'delta',
'hotel',
'india'
]);
}
{ // #2: Remove child nodes of "hotel"
const node = tree.getNodeById('hotel');
t.ok(tree.removeChildNodes(node));
t.equal(node.children.length, 0);
t.equal(tree.nodes.length, nodesLength - 2);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo',
'charlie',
'delta'
]);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('hotel'));
}
{ // #3: Remove child nodes of "charlie"
const node = tree.getNodeById('charlie');
t.ok(tree.removeChildNodes(node));
t.equal(node.children.length, 0);
t.equal(tree.nodes.length, nodesLength - 2 - 4);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo'
]);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('hotel'));
}
{ // #4: Remove child nodes of "<root>"
const node = tree.getNodeById('<root>');
t.ok(tree.removeChildNodes(node));
t.equal(node.children.length, 0);
t.equal(tree.nodes.length, 1);
t.strictSame(tree.state.openNodes.map(node => node.id), []);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('<root>'));
}
{ // #5: Remove child nodes of root node
const rootNode = tree.getRootNode();
t.ok(tree.removeChildNodes(rootNode));
t.equal(tree.nodes.length, 0);
t.strictSame(tree.getSelectedNode(), null);
t.strictSame(tree.getRootNode(), rootNode, 'the root node should not be changed');
}
t.end();
});
test('tree.removeNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
const nodesLength = tree.nodes.length;
// Select a node
t.ok(tree.selectNode(tree.getNodeById('india')));
t.equal(tree.nodes.length, nodesLength);
{ // #1: Pass empty parameters
t.notOk(tree.removeNode());
t.equal(tree.nodes.length, nodesLength);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo',
'charlie',
'delta',
'hotel',
'india'
]);
}
{ // #2: Remove "hotel"
const node = tree.getNodeById('hotel');
t.ok(tree.removeNode(node));
t.equal(tree.nodes.length, nodesLength - 3);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo',
'charlie',
'delta'
]);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('kilo'), 'the next sibling node of "hotel" is "kilo"');
}
{ // #3: Remove "kilo"
const node = tree.getNodeById('kilo');
t.ok(tree.removeNode(node));
t.equal(tree.nodes.length, nodesLength - 3 - 1);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>',
'bravo',
'charlie',
'delta'
]);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('charlie'), 'the previous sibling node of "kilo" is charlie"');
}
{ // #4: Remove "charlie"
const node = tree.getNodeById('charlie');
t.ok(tree.removeNode(node));
t.equal(tree.nodes.length, nodesLength - 3 - 1 - 5);
t.strictSame(tree.state.openNodes.map(node => node.id), [
'<root>'
]);
t.strictSame(tree.getSelectedNode(), tree.getNodeById('bravo'), 'the parent node of "charlie" is "bravo"');
}
{ // #5: Remove "<root>"
const node = tree.getNodeById('<root>');
t.ok(tree.removeNode(node));
t.equal(tree.nodes.length, 0);
t.strictSame(tree.state.openNodes.map(node => node.id), []);
t.strictSame(tree.getSelectedNode(), null, 'no more selected node');
}
{ // #6: Remove root node
const rootNode = tree.getRootNode();
t.notOk(tree.removeNode(rootNode), 'root node cannot be removed');
t.strictSame(tree.getRootNode(), rootNode, 'the root node should not be changed');
}
t.end();
});
test('tree.selectNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
let eventFiredCount = 0;
tree.on('selectNode', (node) => {
++eventFiredCount;
});
// Select Node
t.notOk(tree.selectNode());
t.strictSame(tree.getSelectedNode(), null);
tree.selectNode(tree.getNodeById('<root>'), { silent: true }); // Prevent event from being triggered
t.strictSame(tree.getSelectedNode(), tree.getNodeById('<root>'));
tree.selectNode(tree.getNodeById('bravo'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('bravo'));
tree.selectNode(tree.getNodeById('charlie'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('charlie'));
tree.selectNode(tree.getNodeById('hotel'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('hotel'));
tree.selectNode(tree.getNodeById('delta'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('delta'));
tree.selectNode(tree.getNodeById('india'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('india'));
tree.selectNode(tree.getNodeById('juliet'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('juliet'));
// Check event fired count
t.strictSame(eventFiredCount, 6);
// Close Node
tree.closeNode(tree.getNodeById('india'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('india'));
tree.closeNode(tree.getNodeById('hotel'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('hotel'));
tree.closeNode(tree.getNodeById('bravo'));
t.strictSame(tree.getSelectedNode(), tree.getNodeById('bravo'));
tree.closeNode(tree.getNodeById('<root>'), { silent: true });
t.strictSame(tree.getSelectedNode(), tree.getNodeById('<root>'));
// Check event fired count
t.strictSame(eventFiredCount, 6 + 3);
t.end();
});
test('tree.swapNodes', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
{ // Swap "alpha" and "bravo"
const root = tree.getNodeById('<root>');
const alpha = tree.getNodeById('alpha');
const bravo = tree.getNodeById('bravo');
t.equal(root.children.indexOf(alpha), 0);
t.equal(root.children.indexOf(bravo), 1);
t.ok(tree.swapNodes(alpha, bravo));
t.equal(root.children.indexOf(alpha), 1);
t.equal(root.children.indexOf(bravo), 0);
}
{ // Swap "bravo" and "delta"
const root = tree.getNodeById('<root>');
const bravo = tree.getNodeById('bravo');
const charlie = tree.getNodeById('charlie');
const delta = tree.getNodeById('delta');
t.equal(bravo.parent, root);
t.equal(delta.parent, charlie);
t.notOk(tree.swapNodes(bravo, delta));
t.equal(bravo.parent, root);
t.equal(delta.parent, charlie);
}
{ // Swap "alpha" and "delta"
const root = tree.getNodeById('<root>');
const alpha = tree.getNodeById('alpha');
const bravo = tree.getNodeById('bravo');
const charlie = tree.getNodeById('charlie');
const delta = tree.getNodeById('delta');
t.equal(alpha.parent, root);
t.equal(delta.parent, charlie);
t.ok(tree.swapNodes(alpha, delta));
t.equal(alpha.parent, charlie);
t.equal(delta.parent, root);
}
t.end();
});
test('tree.toggleNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
let eventFiredCount = 0;
tree.on('openNode', (node) => {
++eventFiredCount;
});
tree.on('closeNode', (node) => {
++eventFiredCount;
});
// Toggle Node
t.notOk(tree.toggleNode());
t.equal(tree.nodes.length, 1);
tree.toggleNode(tree.getNodeById('<root>'), { silent: true }); // Prevent event from being triggered
t.equal(tree.nodes.length, 3);
tree.toggleNode(tree.getNodeById('bravo'));
t.equal(tree.nodes.length, 6);
tree.toggleNode(tree.getNodeById('charlie'));
t.equal(tree.nodes.length, 8);
tree.toggleNode(tree.getNodeById('hotel'));
t.equal(tree.nodes.length, 9);
tree.toggleNode(tree.getNodeById('delta'));
t.equal(tree.nodes.length, 11);
tree.toggleNode(tree.getNodeById('india'));
t.equal(tree.nodes.length, 12);
tree.toggleNode(tree.getNodeById('<root>'), { silent: true }); // Prevent event from being triggered
t.equal(tree.nodes.length, 1);
// Check event fired count
t.equal(eventFiredCount, 5);
t.end();
});
test('tree.toString', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
{ // #1: Serialize the tree
const found = tree.toString();
const wanted = '[{"id":null,"children":[{"id":"<root>","children":[],"state":{"depth":0,"open":false,"path":".0","prefixMask":"0","total":0},"label":"<root>"},{"id":"<root>","children":[{"id":"bravo","children":[{"id":"charlie","children":[{"id":"delta","children":[],"state":{"depth":3,"open":false,"path":".0.1.0.0","prefixMask":"0001","total":0},"label":"Delta"},{"id":"delta","children":[],"state":{"depth":3,"open":false,"path":".0.1.0.0","prefixMask":"0001","total":0},"label":"Delta"}],"state":{"depth":2,"open":false,"path":".0.1.0","prefixMask":"000","total":0},"label":"Charlie"},{"id":"charlie","children":[],"state":{"depth":2,"open":false,"path":".0.1.0","prefixMask":"000","total":0},"label":"Charlie"}],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0},"label":"Bravo"},{"id":"bravo","children":[{"id":"hotel","children":[{"id":"india","children":[],"state":{"depth":3,"open":false,"path":".0.1.1.0","prefixMask":"0001","total":0},"label":"India"}],"state":{"depth":2,"open":false,"path":".0.1.1","prefixMask":"000","total":0},"label":"Hotel"}],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0},"label":"Bravo"},{"id":"bravo","children":[],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0},"label":"Bravo"}],"state":{"depth":0,"open":false,"path":".0","prefixMask":"0","total":0},"label":"<root>"}],"state":{"depth":-1,"open":true,"path":"","prefixMask":"","total":1}}]';
t.strictSame(JSON.parse(found), JSON.parse(wanted));
}
{ // #2: Serialize a node
const found = tree.toString(tree.getNodeById('bravo'));
const wanted = '[{"id":"bravo","label":"Bravo","children":[{"id":"charlie","label":"Charlie","children":[{"id":"delta","label":"Delta","children":[],"state":{"depth":3,"open":false,"path":".0.1.0.0","prefixMask":"0001","total":0}},{"id":"delta","label":"Delta","children":[],"state":{"depth":3,"open":false,"path":".0.1.0.0","prefixMask":"0001","total":0}}],"state":{"depth":2,"open":false,"path":".0.1.0","prefixMask":"000","total":0}},{"id":"charlie","label":"Charlie","children":[],"state":{"depth":2,"open":false,"path":".0.1.0","prefixMask":"000","total":0}}],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0}},{"id":"bravo","label":"Bravo","children":[{"id":"hotel","label":"Hotel","children":[{"id":"india","label":"India","children":[],"state":{"depth":3,"open":false,"path":".0.1.1.0","prefixMask":"0001","total":0}}],"state":{"depth":2,"open":false,"path":".0.1.1","prefixMask":"000","total":0}}],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0}},{"id":"bravo","label":"Bravo","children":[],"state":{"depth":1,"open":false,"path":".0.1","prefixMask":"00","total":0}}]';
t.strictSame(JSON.parse(found), JSON.parse(wanted));
}
t.end();
});
test('tree.unfilter', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: true,
data: getTreeData()
});
// Filter
tree.filter();
t.equal(tree.filtered, true);
// Unfilter
tree.unfilter();
t.equal(tree.filtered, false);
const found = tree.flattenChildNodes()
.filter(node => node.state.filtered === undefined)
.map(node => node.label);
const wanted = [
'<root>',
'Alpha',
'Bravo',
'Charlie',
'Delta',
'Echo',
'Foxtrot',
'Golf',
'Hotel',
'India',
'Juliet',
'Kilo'
];
t.strictSame(found, wanted);
t.end();
});
test('tree.update', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
let eventFiredCount = 0;
tree.on('contentWillUpdate', () => {
++eventFiredCount;
});
tree.on('contentDidUpdate', () => {
++eventFiredCount;
});
tree.update();
t.equal(eventFiredCount, 2);
t.end();
});
test('tree.updateNode', (t) => {
const el = getTreeElement();
const tree = new InfiniteTree(el, {
autoOpen: false,
data: getTreeData()
});
{ // #1: Update props
const node = tree.getNodeById('<root>');
const lastUpdated = new Date().getTime();
tree.updateNode(node, {
props: {
lastUpdated: lastUpdated
}
});
const wanted = {
lastUpdated: lastUpdated
};
t.strictSame(node.props, wanted);
}
{ // #2: Update node id
const node = tree.getNodeById('<root>');
t.equal(node.id, '<root>');
t.equal(tree.nodeTable.get('<root>'), node);
t.equal(tree.nodeTable.get('<root.0>'), undefined);
tree.updateNode(node, { id: '<root.0>' });
t.equal(node.id, '<root.0>');
t.equal(tree.nodeTable.get('<root>'), undefined);
t.equal(tree.nodeTable.get('<root.0>'), node);
t.equal(tree.getNodeById('<root>'), null);
t.equal(tree.getNodeById('<root.0>'), node);
tree.updateNode(node, { id: '<root>' });
t.equal(node.id, '<root>');
t.equal(tree.nodeTable.get('<root>'), node);
t.equal(tree.nodeTable.get('<root.0>'), undefined);
t.equal(tree.getNodeById('<root>'), node);
t.equal(tree.getNodeById('<root.0>'), null);
tree.updateNode(node, { id: undefined });
t.equal(node.id, '<root>');
tree.updateNode(node, { id: null });
t.equal(node.id, '<root>');
}
t.end();
});