orgchart
Version:
Simple and direct organization chart(tree-like hierarchy) library based on pure DOM.
1,371 lines (1,112 loc) • 90.9 kB
JavaScript
const chai = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai").default;
const should = chai.should();
chai.use(sinonChai);
require('jsdom-global')();
process.env.ORGCHART_TEST = '1';
const OrgChart = require('../../src/js/orgchart');
const getState = OrgChart.__testing__.getState;
const setState = OrgChart.__testing__.setState;
function query(selector, root) {
return (root || document).querySelector(selector);
}
function queryAll(selector, root) {
return Array.from((root || document).querySelectorAll(selector));
}
function createElementFromHtml(html) {
const template = document.createElement('template');
template.innerHTML = html.trim();
return template.content.firstElementChild;
}
function siblingElements(element, selector) {
if (!element || !element.parentElement) {
return [];
}
return Array.from(element.parentElement.children).filter(function (childEl) {
return childEl !== element && (!selector || childEl.matches(selector));
});
}
function childElements(element, selector) {
return Array.from(element ? element.children : []).filter(function (childEl) {
return !selector || childEl.matches(selector);
});
}
function closestElement(element, selector) {
return element ? element.closest(selector) : null;
}
function firstSibling(element, selector) {
return siblingElements(element, selector)[0] || null;
}
function previousSiblings(element, selector) {
const siblings = [];
let current = element ? element.previousElementSibling : null;
while (current) {
if (!selector || current.matches(selector)) {
siblings.push(current);
}
current = current.previousElementSibling;
}
return siblings;
}
function nextSiblings(element, selector) {
const siblings = [];
let current = element ? element.nextElementSibling : null;
while (current) {
if (!selector || current.matches(selector)) {
siblings.push(current);
}
current = current.nextElementSibling;
}
return siblings;
}
function addClasses(element, classNames) {
if (!element) {
return element;
}
classNames.split(/\s+/).filter(Boolean).forEach(function (className) {
element.classList.add(className);
});
return element;
}
function removeClasses(element, classNames) {
if (!element) {
return element;
}
classNames.split(/\s+/).filter(Boolean).forEach(function (className) {
element.classList.remove(className);
});
return element;
}
function matchesSelector(element, selector) {
return !!(element && element.matches(selector));
}
function setOneTimeListener(element, type, data, listener) {
function once(event) {
event.data = data;
element.removeEventListener(type, once);
listener.call(element, event);
}
element.addEventListener(type, once);
}
function triggerEvent(element, type) {
element.dispatchEvent(new window.Event(type, { bubbles: true }));
}
function refreshNodeReferences(chartEl) {
$laolao = query('#n1', chartEl);
$bomiao = query('#n2', chartEl);
$sumiao = query('#n3', chartEl);
$hongmiao = query('#n4', chartEl);
$tiehua = query('#n5', chartEl);
$heihei = query('#n6', chartEl);
$pangpang = query('#n7', chartEl);
$dandan = query('#n8', chartEl);
$erdan = query('#n9', chartEl);
$sandan = query('#n10', chartEl);
}
describe('orgchart -- unit tests', function () {
let $container;
const ds = {
'id': 'n1',
'name': 'Lao Lao',
'title': 'general manager',
'children': [
{ 'id': 'n2', 'name': 'Bo Miao', 'title': 'department manager' },
{ 'id': 'n3', 'name': 'Su Miao', 'title': 'department manager',
'children': [
{ 'id': 'n5', 'name': 'Tie Hua', 'title': 'senior engineer',
'children': [
{ 'id': 'n8', 'name': 'Dan Dan', 'title': 'engineer' }
]
},
{ 'id': 'n6', 'name': 'Hei Hei', 'title': 'senior engineer',
'children': [
{ 'id': 'n9', 'name': 'Er Dan', 'title': 'engineer' }
]
},
{ 'id': 'n7', 'name': 'Pang Pang', 'title': 'senior engineer',
'children': [
{ 'id': 'n10', 'name': 'San Dan', 'title': 'engineer' }
]
}
]
},
{ 'id': 'n4', 'name': 'Hong Miao', 'title': 'department manager' }
]
};
const baseDs = JSON.stringify({
'id': 'n1',
'name': 'Lao Lao',
'title': 'general manager',
'children': [
{ 'id': 'n2', 'name': 'Bo Miao', 'title': 'department manager' },
{ 'id': 'n3', 'name': 'Su Miao', 'title': 'department manager',
'children': [
{ 'id': 'n5', 'name': 'Tie Hua', 'title': 'senior engineer',
'children': [
{ 'id': 'n8', 'name': 'Dan Dan', 'title': 'engineer' }
]
},
{ 'id': 'n6', 'name': 'Hei Hei', 'title': 'senior engineer',
'children': [
{ 'id': 'n9', 'name': 'Er Dan', 'title': 'engineer' }
]
},
{ 'id': 'n7', 'name': 'Pang Pang', 'title': 'senior engineer',
'children': [
{ 'id': 'n10', 'name': 'San Dan', 'title': 'engineer' }
]
}
]
},
{ 'id': 'n4', 'name': 'Hong Miao', 'title': 'department manager' }
]
});
let oc = {};
const hierarchy = {
id: 'n1',
children: [
{ id: 'n2' },
{ id: 'n3',
children: [
{ id: 'n5',
children: [
{ id: 'n8' }
]
},
{ id: 'n6',
children: [
{ id: 'n9' }
]
},
{ id: 'n7',
children: [
{ id: 'n10' }
]
}
]
},
{ id: 'n4' }
]
};
let $laolao;
let $bomiao;
let $sumiao;
let $hongmiao;
let $chunmiao;
let $tiehua;
let $heihei;
let $pangpang;
let $dandan;
let $erdan;
let $sandan;
beforeEach(function () {
document.body.innerHTML = '<div id="chart-container"></div>';
$container = document.getElementById('chart-container');
oc = new OrgChart({
chartContainer: '#chart-container',
'data': ds,
'nodeContent': 'title'
}),
$laolao = document.getElementById('n1'),
$bomiao = document.getElementById('n2'),
$sumiao = document.getElementById('n3'),
$hongmiao = document.getElementById('n4'),
$tiehua = document.getElementById('n5'),
$heihei = document.getElementById('n6'),
$pangpang = document.getElementById('n7'),
$dandan = document.getElementById('n8');
$erdan = document.getElementById('n9');
$sandan = document.getElementById('n10');
});
afterEach(function () {
$laolao = $bomiao = $sumiao = $hongmiao = $tiehua = $heihei = $pangpang = $dandan = $erdan = $sandan = null;
$container.innerHTML = '';
});
it('loopChart()', function () {
oc.loopChart(query('.orgchart')).should.deep.equal(hierarchy);
});
it('exposes native chartContainer and chart instance properties', function () {
oc.chartContainer.should.equal($container);
oc.chartContainer.firstElementChild.should.equal(oc.chart);
oc.chart.should.have.property('nodeType', 1);
});
it('buildJsonDS() accepts a native li element', function () {
const ulContainer = document.createElement('ul');
ulContainer.innerHTML = '<li data-id="root" data-title="chief">Root<ul><li data-id="child">Child</li></ul></li>';
oc.buildJsonDS(ulContainer.firstElementChild).should.deep.equal({
id: 'root',
title: 'chief',
name: 'Root',
relationship: '001',
children: [
{
id: 'child',
name: 'Child',
relationship: '100'
}
]
});
});
it('getHierarchy()', function () {
oc.getHierarchy().should.deep.equal(hierarchy);
const oc2 = new OrgChart({
chartContainer: '#chart-container',
'data': { name: 'Lao Lao',
'children': [
{ name: 'Bo Miao' }
]
}
});
oc2.getHierarchy().should.include('Error');
oc2.chart.innerHTML = "";
oc2.getHierarchy().should.include('Error');
oc2.chart = null;
oc2.getHierarchy().should.include('Error');
});
it('triggerShowEvent() accepts a native node', function () {
const eventSpy = sinon.spy();
$sumiao.addEventListener('show-children.orgchart', eventSpy);
oc.triggerShowEvent($sumiao, 'children');
eventSpy.should.have.been.calledOnce;
eventSpy.firstCall.args[0].type.should.equal('show-children.orgchart');
});
it('triggerHideEvent() accepts a native node', function () {
const eventSpy = sinon.spy();
$sumiao.addEventListener('hide-children.orgchart', eventSpy);
oc.triggerHideEvent($sumiao, 'children');
eventSpy.should.have.been.calledOnce;
eventSpy.firstCall.args[0].type.should.equal('hide-children.orgchart');
});
it('getNodeState()', function () {
const stateDs = JSON.parse(baseDs);
let chartEl;
let laolaoEl;
let bomiaoEl;
let sumiaoEl;
let tiehuaEl;
let heiheiEl;
let pangpangEl;
let dandanEl;
$container.innerHTML = '';
oc = new OrgChart({
chartContainer: '#chart-container',
data: stateDs,
nodeContent: 'title',
visibleLevel: 2,
verticalLevel: 3
});
chartEl = oc.chart;
laolaoEl = query('#n1', chartEl);
bomiaoEl = query('#n2', chartEl);
sumiaoEl = query('#n3', chartEl);
tiehuaEl = query('#n5', chartEl);
heiheiEl = query('#n6', chartEl);
pangpangEl = query('#n7', chartEl);
dandanEl = query('#n8', chartEl);
oc.getNodeState(laolaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->self');
oc.getNodeState(laolaoEl, 'parent').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->parent');
oc.getNodeState(laolaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->children');
oc.getNodeState(laolaoEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->siblings');
oc.getNodeState(bomiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->self');
oc.getNodeState(bomiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->parent');
oc.getNodeState(bomiaoEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'bomiao->children');
oc.getNodeState(bomiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->siblings');
oc.getNodeState(sumiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->self');
oc.getNodeState(sumiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->parent');
oc.getNodeState(sumiaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'sumiao->children');
oc.getNodeState(sumiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->siblings');
oc.getNodeState(tiehuaEl).should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->self');
oc.getNodeState(tiehuaEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'tiehua->parent');
oc.getNodeState(tiehuaEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->children');
oc.getNodeState(tiehuaEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->siblings');
oc.getNodeState(heiheiEl).should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->self');
oc.getNodeState(heiheiEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'heihei->parent');
oc.getNodeState(heiheiEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->children');
oc.getNodeState(heiheiEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->siblings');
oc.getNodeState(pangpangEl).should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->self');
oc.getNodeState(pangpangEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'pangpang->parent');
oc.getNodeState(pangpangEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->children');
oc.getNodeState(pangpangEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->siblings');
oc.getNodeState(dandanEl).should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->self');
oc.getNodeState(dandanEl, 'parent').should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->parent');
oc.getNodeState(dandanEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->children');
oc.getNodeState(dandanEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->siblings');
});
it('triggerInitEvent() calls initCompleted with the native chart element', function () {
const originalMutationObserver = global.MutationObserver;
const originalWindowMutationObserver = window.MutationObserver;
const observeSpy = sinon.spy();
const disconnectSpy = sinon.spy();
const initCompletedSpy = sinon.spy();
const fakeMutationObserver = function (callback) {
this.observe = function (target, options) {
observeSpy(target, options);
callback([
{
addedNodes: [oc.chart]
}
]);
};
this.disconnect = disconnectSpy;
};
global.MutationObserver = fakeMutationObserver;
window.MutationObserver = fakeMutationObserver;
oc.options.initCompleted = initCompletedSpy;
oc.triggerInitEvent();
observeSpy.should.have.been.calledOnce;
observeSpy.firstCall.args[0].should.equal($container);
observeSpy.firstCall.args[1].should.deep.equal({ childList: true });
disconnectSpy.should.have.been.calledOnce;
initCompletedSpy.should.have.been.calledOnce;
initCompletedSpy.firstCall.args[0].should.equal(oc.chart);
global.MutationObserver = originalMutationObserver;
window.MutationObserver = originalWindowMutationObserver;
});
it('triggerInitEvent() triggers init.orgchart on the chart collection', function () {
const originalMutationObserver = global.MutationObserver;
const originalWindowMutationObserver = window.MutationObserver;
const eventSpy = sinon.spy();
const fakeMutationObserver = function (callback) {
this.observe = function () {
callback([
{
addedNodes: [oc.chart]
}
]);
};
this.disconnect = function () {};
};
global.MutationObserver = fakeMutationObserver;
window.MutationObserver = fakeMutationObserver;
oc.chart.addEventListener('init.orgchart', eventSpy);
oc.triggerInitEvent();
eventSpy.should.have.been.calledOnce;
eventSpy.firstCall.args[0].type.should.equal('init.orgchart');
global.MutationObserver = originalMutationObserver;
window.MutationObserver = originalWindowMutationObserver;
});
it('handleCompactNodes() tags compact nodes by compact-ancestor parity', function () {
const compactRootEl = document.createElement('div');
const compactChildEl = document.createElement('div');
const compactGrandchildEl = document.createElement('div');
compactRootEl.className = 'node compact';
compactChildEl.className = 'node compact';
compactGrandchildEl.className = 'node compact';
compactRootEl.appendChild(compactChildEl);
compactChildEl.appendChild(compactGrandchildEl);
oc.chart.appendChild(compactRootEl);
oc.handleCompactNodes();
compactRootEl.classList.contains('even').should.equal(true);
compactChildEl.classList.contains('odd').should.equal(true);
compactGrandchildEl.classList.contains('even').should.equal(true);
});
it('getRelatedNodes()', function () {
oc.getRelatedNodes().should.deep.equal([]);
oc.getRelatedNodes({}, 'children').should.deep.equal([]);
oc.getRelatedNodes(query('.hierarchy', oc.chart), 'children').should.deep.equal([]);
oc.getRelatedNodes(query('.node', oc.chart), 'child').should.deep.equal([]);
should.equal(oc.getRelatedNodes($laolao, 'parent'), null);
oc.getRelatedNodes($laolao, 'children').should.have.members([$bomiao, $sumiao, $hongmiao]);
oc.getRelatedNodes($laolao, 'siblings').should.deep.equal([]);
oc.getRelatedNodes($bomiao, 'parent').should.equal($laolao);
oc.getRelatedNodes($bomiao, 'children').should.have.lengthOf(0);
oc.getRelatedNodes($bomiao, 'siblings').should.have.members([$sumiao, $hongmiao]);
});
it('getParent()', function () {
should.equal(oc.getParent(), null);
should.equal(oc.getParent({}), null);
should.equal(oc.getParent(query('.hierarchy', oc.chart)), null);
should.equal(oc.getParent($laolao), null);
oc.getParent($bomiao).should.equal($laolao);
oc.getParent($sandan).should.equal($pangpang);
});
it('getSiblings()', function () {
oc.getSiblings().should.deep.equal([]);
oc.getSiblings({}).should.deep.equal([]);
oc.getSiblings(query('.hierarchy', oc.chart)).should.deep.equal([]);
oc.getSiblings($laolao).should.deep.equal([]);
oc.getSiblings($bomiao).should.have.members([$sumiao, $hongmiao]);
oc.getSiblings($sandan).should.deep.equal([]);
});
it('getChildren()', function () {
oc.getChildren().should.deep.equal([]);
oc.getChildren({}).should.deep.equal([]);
oc.getChildren(query('.hierarchy', oc.chart)).should.deep.equal([]);
oc.getChildren($laolao).should.have.members([$bomiao, $sumiao, $hongmiao]);
oc.getChildren($bomiao).should.deep.equal([]);
oc.getChildren($sumiao).should.have.members([$tiehua, $heihei, $pangpang]);
oc.getChildren($sandan).should.deep.equal([]);
});
it('hideParent()', function () {
const spy = sinon.spy(oc, 'hideParent');
const spy2 = sinon.spy(oc, 'hideSiblings');
oc.hideParent($heihei);
setOneTimeListener($sumiao, 'transitionend', null, function (event) {
event.parent = parentEl;
spy.call(oc, event);
});
spy.should.have.been.callCount(2);
spy.getCall(0).args[0].should.equal($heihei);
spy.getCall(1).args[0].should.equal($sumiao);
spy2.should.have.been.callCount(2);
spy2.getCall(0).args[0].should.equal($heihei);
spy2.getCall(1).args[0].should.equal($sumiao);
});
it('hideParent() hides the transitioned parent node', function () {
oc.hideParent($sumiao);
triggerEvent($laolao, 'transitionend');
$laolao.classList.contains('sliding').should.equal(false);
$laolao.classList.contains('hidden').should.equal(true);
});
it('hideParent() clears inAjax when the parent node contains a spinner', function () {
const spinnerEl = document.createElement('i');
spinnerEl.className = 'spinner';
$sumiao.appendChild(spinnerEl);
setState(oc.chart, 'inAjax', true);
oc.hideParent($heihei);
getState(oc.chart, 'inAjax').should.be.false;
spinnerEl.remove();
});
it('hideParentEnd()', function () {
const parentEl = firstSibling(closestElement($heihei, '.nodes'), '.node');
addClasses($sumiao, 'sliding slide-down');
oc.hideParentEnd({ animatedNode: $sumiao, parent: parentEl });
matchesSelector($sumiao, 'sliding').should.be.false;
matchesSelector(parentEl, '.hidden').should.be.true;
});
it('hideParentEnd() accepts native animated and parent payloads', function () {
addClasses($sumiao, 'sliding');
removeClasses($sumiao, 'hidden');
oc.hideParentEnd({
animatedNode: $sumiao,
parent: $sumiao
});
$sumiao.classList.contains('sliding').should.equal(false);
$sumiao.classList.contains('hidden').should.equal(true);
});
it('hideParentEnd() accepts wrapped native animated and parent payloads', function () {
addClasses($sumiao, 'sliding');
oc.hideParentEnd({
originalEvent: {
target: $sumiao,
parent: $heihei
}
});
$sumiao.classList.contains('sliding').should.equal(false);
$heihei.classList.contains('hidden').should.equal(true);
});
it('showParent()', function () {
const spy = sinon.spy(oc, 'repaint');
addClasses(closestElement($heihei, '.hierarchy'), 'isAncestorsCollapsed');
addClasses($sumiao, 'slide-down hidden');
addClasses(closestElement($sumiao, '.hierarchy'), 'isAncestorsCollapsed');
addClasses($laolao, 'slide-down hidden');
oc.showParent($heihei);
spy.should.have.been.called;
matchesSelector(closestElement($heihei, '.hierarchy'), '.isAncestorsCollapsed').should.be.false;
matchesSelector($sumiao, '.slide-down, .hidden').should.be.false;
matchesSelector(closestElement($sumiao, '.hierarchy'), '.isAncestorsCollapsed').should.be.true;
matchesSelector($laolao, '.slide-down.hidden').should.be.true;
matchesSelector($sumiao, '.sliding').should.be.true;
matchesSelector($sumiao, '.slide-down').should.be.false;
});
it('showParentEnd()', function () {
const spy = sinon.spy(oc, 'showParentEnd');
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
addClasses($sumiao, 'sliding');
removeClasses($sumiao, 'slide-down');
setOneTimeListener($sumiao, 'transitionend', null, function (event) {
event.node = $heihei;
spy.call(oc, event);
});
triggerEvent($sumiao, 'transitionend');
spy.should.have.been.called;
matchesSelector($sumiao, '.sliding').should.be.false;
spy2.should.have.been.calledWith($heihei);
spy3.should.not.have.been.called;
});
it('showParentEnd() accepts native animated and node payloads', function () {
addClasses($sumiao, 'sliding');
oc.showParentEnd({
animatedNode: $sumiao,
node: $heihei
});
$sumiao.classList.contains('sliding').should.equal(false);
});
it('showParentEnd() accepts wrapped native animated and node payloads', function () {
addClasses($sumiao, 'sliding');
oc.showParentEnd({
originalEvent: {
target: $sumiao,
node: $heihei
}
});
$sumiao.classList.contains('sliding').should.equal(false);
});
it('hideChildren()', function () {
const spy = sinon.spy(oc, 'repaint');
oc.hideChildren($sumiao);
spy.should.have.been.called;
matchesSelector(closestElement($sumiao, '.hierarchy'), '.isChildrenCollapsed').should.be.true;
childElements(firstSibling($sumiao, '.nodes'), '.isCollapsedDescendant').should.lengthOf(3);
matchesSelector($tiehua, '.sliding.slide-up').should.be.true;
matchesSelector($heihei, '.sliding.slide-up').should.be.true;
matchesSelector($pangpang, '.sliding.slide-up').should.be.true;
matchesSelector($erdan, '.sliding.slide-up').should.be.true;
});
it('hideChildrenEnd()', function () {
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
addClasses($tiehua, 'sliding slide-up');
oc.hideChildrenEnd({ animatedNodes: $tiehua, node: $sumiao });
matchesSelector($tiehua, '.sliding').should.be.false;
matchesSelector(closestElement($tiehua, '.nodes'), '.hidden').should.be.true;
spy2.should.have.been.calledWith($sumiao);
spy3.should.not.have.been.called;
});
it('hideChildrenEnd() accepts direct animated and node payloads', function () {
const bottomEdgeEl = query('.bottomEdge', $sumiao);
const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
removeClasses($tiehua, 'hidden');
addClasses($tiehua, 'sliding');
removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
bottomEdgeEl.classList.add(oc.options.icons.collapseToDown);
oc.hideChildrenEnd({ animatedNodes: [$tiehua], node: $sumiao });
$tiehua.classList.contains('sliding').should.equal(false);
closestElement($tiehua, '.nodes').classList.contains('hidden').should.equal(true);
verticalSpy.should.have.been.calledWith(bottomEdgeEl);
verticalSpy.restore();
});
it('hideChildrenEnd() accepts wrapped native animated and node payloads', function () {
const bottomEdgeEl = query('.bottomEdge', $sumiao);
const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
removeClasses($tiehua, 'hidden');
addClasses($tiehua, 'sliding');
removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
bottomEdgeEl.classList.add(oc.options.icons.collapseToDown);
oc.hideChildrenEnd({
originalEvent: {
animatedNodes: [$tiehua],
node: $sumiao
}
});
$tiehua.classList.contains('sliding').should.equal(false);
closestElement($tiehua, '.nodes').classList.contains('hidden').should.equal(true);
verticalSpy.should.have.been.calledWith(bottomEdgeEl);
verticalSpy.restore();
});
it('showChildren()', function () {
const spy = sinon.spy(oc, 'repaint');
const descendantsWrapperEl = firstSibling($sumiao, '.nodes');
queryAll('.node', descendantsWrapperEl).forEach(function (nodeEl) {
addClasses(nodeEl, 'slide-up');
});
queryAll('.nodes', descendantsWrapperEl).forEach(function (nodeEl) {
addClasses(nodeEl, 'hidden');
});
addClasses(descendantsWrapperEl, 'hidden');
oc.showChildren($sumiao);
spy.should.have.been.calledWith($tiehua);
matchesSelector($tiehua, '.sliding:not(.slide-up)').should.be.true;
matchesSelector($heihei, '.sliding:not(.slide-up)').should.be.true;
matchesSelector($pangpang, '.sliding:not(.slide-up)').should.be.true;
matchesSelector($erdan, '.slide-up:not(.sliding)').should.be.true;
});
it('showChildrenEnd()', function () {
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
addClasses($tiehua, 'sliding');
oc.showChildrenEnd({ node: $sumiao, animatedNodes: $tiehua });
matchesSelector($tiehua, '.sliding').should.be.false;
spy2.should.have.been.calledWith($sumiao);
spy3.should.not.have.been.called;
});
it('showChildrenEnd() accepts direct animated and node payloads', function () {
addClasses($tiehua, 'sliding');
oc.showChildrenEnd({
node: $sumiao,
animatedNodes: [$tiehua]
});
$tiehua.classList.contains('sliding').should.equal(false);
});
it('showChildrenEnd() accepts wrapped native animated and node payloads', function () {
addClasses($tiehua, 'sliding');
oc.showChildrenEnd({
originalEvent: {
node: $sumiao,
animatedNodes: [$tiehua]
}
});
$tiehua.classList.contains('sliding').should.equal(false);
});
it('showSiblingsEnd() accepts direct visible and node payloads', function () {
const topEdgeEl = query('.topEdge', $heihei);
const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
addClasses($tiehua, 'sliding');
addClasses($pangpang, 'sliding');
topEdgeEl.classList.add(oc.options.icons.expandToUp);
oc.showSiblingsEnd({
node: $heihei,
visibleNodes: [$tiehua, $pangpang]
});
$tiehua.classList.contains('sliding').should.equal(false);
$pangpang.classList.contains('sliding').should.equal(false);
switchSpy.should.have.been.calledWith($heihei);
topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
switchSpy.restore();
});
it('showSiblingsEnd() accepts wrapped native visible and node payloads', function () {
const topEdgeEl = query('.topEdge', $heihei);
const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
addClasses($tiehua, 'sliding');
addClasses($pangpang, 'sliding');
topEdgeEl.classList.add(oc.options.icons.expandToUp);
oc.showSiblingsEnd({
originalEvent: {
node: $heihei,
visibleNodes: [$tiehua, $pangpang]
}
});
$tiehua.classList.contains('sliding').should.equal(false);
$pangpang.classList.contains('sliding').should.equal(false);
switchSpy.should.have.been.calledWith($heihei);
topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
switchSpy.restore();
});
it('showRelatedParentEnd() removes sliding from a native animated node payload', function () {
addClasses($sumiao, 'sliding');
oc.showRelatedParentEnd({ animatedNode: $sumiao });
$sumiao.classList.contains('sliding').should.equal(false);
});
it('showRelatedParentEnd() accepts a wrapped native animated node payload', function () {
addClasses($sumiao, 'sliding');
oc.showRelatedParentEnd({
originalEvent: {
target: $sumiao
}
});
$sumiao.classList.contains('sliding').should.equal(false);
});
it('showSiblings() accepts a native node and reveals the requested side with animation', function () {
const repaintSpy = sinon.spy(oc, 'repaint');
addClasses(closestElement($tiehua, '.hierarchy'), 'hidden isCollapsedSibling left-sibs');
addClasses($tiehua, 'slide-right');
addClasses(closestElement($heihei, '.hierarchy'), 'isSiblingsCollapsed left-sibs isAncestorsCollapsed');
addClasses($sumiao, 'hidden slide-down');
oc.showSiblings($heihei, 'left');
repaintSpy.should.have.been.calledWith($sumiao);
closestElement($tiehua, '.hierarchy').classList.contains('hidden').should.equal(false);
closestElement($tiehua, '.hierarchy').classList.contains('isCollapsedSibling').should.equal(false);
closestElement($heihei, '.hierarchy').classList.contains('left-sibs').should.equal(false);
closestElement($heihei, '.hierarchy').classList.contains('isSiblingsCollapsed').should.equal(false);
$tiehua.classList.contains('sliding').should.equal(true);
$tiehua.classList.contains('slide-right').should.equal(false);
$sumiao.classList.contains('hidden').should.equal(false);
$sumiao.classList.contains('sliding').should.equal(true);
$sumiao.classList.contains('slide-down').should.equal(false);
repaintSpy.restore();
});
describe('hideSiblings()', function () {
context('when passing only one parameter -- node', function () {
it('should hide all the sibling nodes and their descendants', function () {
oc.hideSiblings($heihei);
matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
matchesSelector($dandan, '.sliding.slide-right').should.be.true;
matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
matchesSelector($sandan, '.sliding.slide-left').should.be.true;
matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed').should.be.true;
siblingElements(closestElement($heihei, '.hierarchy'), '.isChildrenCollapsed.isCollapsedSibling').should.lengthOf(2);
});
it('clears inAjax when a sibling hierarchy contains a spinner', function () {
const spinnerEl = document.createElement('i');
spinnerEl.className = 'spinner';
closestElement($tiehua, '.hierarchy').appendChild(spinnerEl);
setState(oc.chart, 'inAjax', true);
oc.hideSiblings($heihei);
getState(oc.chart, 'inAjax').should.be.false;
spinnerEl.remove();
});
});
context('when passing two parameters -- node and direction', function () {
it('hide the left side sibling nodes and their descendants', function () {
oc.hideSiblings($heihei, 'left');
matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.left-sibs').should.be.true;
matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
matchesSelector($dandan, '.sliding.slide-right').should.be.true;
matchesSelector($pangpang, '.sliding').should.be.false;
});
it('hide the right side sibling nodes and their descendants', function () {
oc.hideSiblings($heihei, 'right');
matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.right-sibs').should.be.true;
matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
matchesSelector($sandan, '.sliding.slide-left').should.be.true;
matchesSelector($tiehua, '.sliding').should.be.false;
});
});
});
describe('hideSiblingsEnd()', function () {
context('when invoking transitionend event without specifying direction', function () {
it('clean up final classList for hidden siblings', function () {
const spy = sinon.spy(oc, 'hideSiblingsEnd');
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
const nodeContainerEl = closestElement($heihei, '.hierarchy');
oc.hideSiblings($heihei);
setOneTimeListener($tiehua, 'transitionend', {
'node': $heihei,
'nodeContainer': nodeContainerEl,
'direction': undefined,
'animatedNodes': $tiehua
}, spy.bind(oc));
triggerEvent($tiehua, 'transitionend');
spy.should.have.been.called;
matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
matchesSelector($dandan, '.slide-up:not(.sliding, .slide-right)').should.be.true;
matchesSelector($pangpang, '.slide-left:not(.sibling)').should.be.true;
matchesSelector($sandan, '.slide-up:not(.sliding, .slide-left)').should.be.true;
siblingElements(nodeContainerEl, '.hidden').should.lengthOf(2);
siblingElements(nodeContainerEl).reduce(function (count, siblingEl) {
return count + queryAll('.nodes.hidden', siblingEl).length;
}, 0).should.equal(2);
spy2.should.have.been.calledWith($heihei);
spy3.should.not.have.been.called;
});
});
context('when invoking transitionend event with specifying direction', function () {
it('clean up final classList for left side hidden siblings', function () {
const spy = sinon.spy(oc, 'hideSiblingsEnd');
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
const nodeContainerEl = closestElement($heihei, '.hierarchy');
oc.hideSiblings($heihei, 'left');
setOneTimeListener($tiehua, 'transitionend', {
'node': $heihei,
'nodeContainer': nodeContainerEl,
'direction': 'left',
'animatedNodes': $tiehua
}, spy.bind(oc));
triggerEvent($tiehua, 'transitionend');
spy.should.have.been.called;
matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
matchesSelector($dandan, '.slide-up:not(.slide-right)').should.be.true;
matchesSelector($pangpang, '.slide-left').should.be.false;
matchesSelector($sandan, '.slide-up').should.be.false;
previousSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
return count + queryAll('.nodes.hidden', siblingEl).length;
}, 0).should.equal(1);
nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
return count + queryAll('.nodes.hidden', siblingEl).length;
}, 0).should.equal(0);
spy2.should.have.been.calledWith($heihei);
spy3.should.not.have.been.called;
});
it('clean up final classList for right side hidden siblings', function () {
const spy2 = sinon.spy(oc, 'isInAction');
const spy3 = sinon.spy(oc, 'switchVerticalArrow');
const nodeContainerEl = closestElement($heihei, '.hierarchy');
oc.hideSiblings($heihei, 'right');
oc.hideSiblingsEnd({
node: $heihei,
nodeContainer: nodeContainerEl,
direction: 'right',
animatedNodes: $tiehua
});
matchesSelector($tiehua, '.slide-right').should.be.false;
matchesSelector($dandan, '.slide-up').should.be.false;
matchesSelector($pangpang, '.slide-left:not(.slidiing)').should.be.true;
matchesSelector($sandan, '.slide-up:not(.slide-left)').should.be.true;
nextSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
return count + queryAll('.nodes.hidden', siblingEl).length;
}, 0).should.equal(1);
previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
return count + queryAll('.nodes.hidden', siblingEl).length;
}, 0).should.equal(0);
spy2.should.have.been.calledWith($heihei);
spy3.should.not.have.been.called;
});
it('accepts direct node, container, and animated payloads', function () {
const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
const nodeContainerEl = closestElement($heihei, '.hierarchy');
oc.hideSiblings($heihei, 'left');
addClasses($tiehua, 'sliding');
removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
oc.hideSiblingsEnd({
node: $heihei,
nodeContainer: nodeContainerEl,
direction: 'left',
animatedNodes: [$tiehua]
});
$tiehua.classList.contains('sliding').should.equal(false);
nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
horizontalSpy.should.have.been.calledWith($heihei);
horizontalSpy.restore();
});
it('accepts wrapped native node, container, and animated payloads', function () {
const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
const nodeContainerEl = closestElement($heihei, '.hierarchy');
oc.hideSiblings($heihei, 'left');
addClasses($tiehua, 'sliding');
removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
oc.hideSiblingsEnd({
originalEvent: {
node: $heihei,
nodeContainer: nodeContainerEl,
direction: 'left',
animatedNodes: [$tiehua]
}
});
$tiehua.classList.contains('sliding').should.equal(false);
nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
horizontalSpy.should.have.been.calledWith($heihei);
horizontalSpy.restore();
});
});
});
it('nodeEnterLeaveHandler() accepts a native node payload for relationship checks', function () {
const spy = sinon.spy(oc, 'switchHorizontalArrow');
oc.nodeEnterLeaveHandler({ type: 'mouseenter', node: $sumiao });
spy.should.have.been.calledWith($sumiao);
});
it('nodeEnterLeaveHandler() removes arrow classes from a native node payload on mouseleave', function () {
const nodeEl = $sumiao;
Array.from(nodeEl.querySelectorAll('.edge')).forEach(function (edgeEl) {
edgeEl.classList.add(
oc.options.icons.expandToUp,
oc.options.icons.collapseToDown,
oc.options.icons.collapseToLeft,
oc.options.icons.expandToRight
);
});
oc.nodeEnterLeaveHandler({ type: 'mouseleave', node: nodeEl });
Array.from(nodeEl.querySelectorAll('.edge')).forEach(function (edgeEl) {
edgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
edgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(false);
edgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
edgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
});
});
it('nodeEnterLeaveHandler() accepts wrapped native node payloads', function () {
const spy = sinon.spy(oc, 'switchHorizontalArrow');
oc.nodeEnterLeaveHandler({
originalEvent: {
type: 'mouseenter',
currentTarget: $sumiao
}
});
spy.should.have.been.calledWith($sumiao);
spy.restore();
});
it('HideFirstParentEnd() accepts a native top edge payload', function () {
const topEdgeEl = query('.topEdge', $heihei);
const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
topEdgeEl.classList.add(oc.options.icons.expandToUp);
oc.HideFirstParentEnd({ topEdge: topEdgeEl });
verticalSpy.should.have.been.calledWith(topEdgeEl);
horizontalSpy.should.have.been.calledWith($heihei);
verticalSpy.restore();
horizontalSpy.restore();
});
it('HideFirstParentEnd() accepts a wrapped native top edge payload', function () {
const topEdgeEl = query('.topEdge', $heihei);
const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
topEdgeEl.classList.add(oc.options.icons.expandToUp);
oc.HideFirstParentEnd({
originalEvent: {
topEdge: topEdgeEl
}
});
verticalSpy.should.have.been.calledWith(topEdgeEl);
horizontalSpy.should.have.been.calledWith($heihei);
verticalSpy.restore();
horizontalSpy.restore();
});
it('nodeClickHandler() focuses the native node payload and clears previous focus', function () {
addClasses($bomiao, 'focused');
oc.nodeClickHandler({ node: $sumiao });
$bomiao.classList.contains('focused').should.equal(false);
$sumiao.classList.contains('focused').should.equal(true);
});
it('nodeClickHandler() focuses a wrapped native node payload', function () {
addClasses($bomiao, 'focused');
oc.nodeClickHandler({
originalEvent: {
currentTarget: $sumiao
}
});
$bomiao.classList.contains('focused').should.equal(false);
$sumiao.classList.contains('focused').should.equal(true);
});
it('init() clears focused nodes when clicking the chart background', function () {
addClasses($bomiao, 'focused');
addClasses($sumiao, 'focused');
oc.chart.dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
$bomiao.classList.contains('focused').should.equal(false);
$sumiao.classList.contains('focused').should.equal(false);
});
it('init() clears focused nodes from a wrapped native background click payload', function () {
const wrappedClickEvent = new window.Event('click', { bubbles: true });
addClasses($bomiao, 'focused');
addClasses($sumiao, 'focused');
wrappedClickEvent.originalEvent = {
target: oc.chart
};
oc.chart.dispatchEvent(wrappedClickEvent);
$bomiao.classList.contains('focused').should.equal(false);
$sumiao.classList.contains('focused').should.equal(false);
});
it('init() appends the export button only once', function () {
let oc2;
Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
exportButtonEl.remove();
});
oc2 = new OrgChart({
chartContainer: '#chart-container',
data: ds,
exportButton: true
});
document.querySelectorAll('.oc-export-btn').length.should.equal(1);
oc2.init();
document.querySelectorAll('.oc-export-btn').length.should.equal(1);
Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
exportButtonEl.remove();
});
});
it('attachExportButton() creates a native export button that calls export()', function () {
const exportStub = sinon.stub(oc, 'export');
Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
exportButtonEl.remove();
});
oc.attachExportButton();
document.querySelectorAll('.oc-export-btn').length.should.equal(1);
document.querySelector('.oc-export-btn').dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
exportStub.should.have.been.calledOnce;
exportStub.restore();
Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
exportButtonEl.remove();
});
});
it('topEdgeClickHandler() passes a native node to hideParent()', function () {
const spy = sinon.spy(oc, 'hideParent');
const spy2 = sinon.spy(oc, 'triggerHideEvent');
oc.topEdgeClickHandler({ edge: query('.topEdge', $heihei), node: $heihei });
spy.should.have.been.calledWith($heihei);
spy2.should.have.been.called;
});
it('topEdgeClickHandler() accepts wrapped native edge payloads', function () {
const spy = sinon.spy(oc, 'hideParent');
oc.topEdgeClickHandler({
originalEvent: {
currentTarget: $heihei,
target: query('.topEdge', $heihei)
}
});
spy.should.have.been.calledWith($heihei);
spy.restore();
});
it('bottomEdgeClickHandler() passes a native node to hideChildren()', function () {
const spy = sinon.spy(oc, 'hideChildren');
const spy2 = sinon.spy(oc, 'triggerHideEvent');
oc.bottomEdgeClickHandler({ edge: query('.bottomEdge', $sumiao), node: $sumiao });
spy.should.have.been.calledWith($sumiao);
spy2.should.have.been.called;
});
it('bottomEdgeClickHandler() accepts wrapped native node payloads', function () {
const spy = sinon.spy(oc, 'hideChildren');
oc.bottomEdgeClickHandler({
originalEvent: {
currentTarget: $sumiao
}
});
spy.should.have.been.calledWith($sumiao);
spy.restore();
});
it('hEdgeClickHandler() passes a native node to hideSiblings()', function () {
const spy = sinon.spy(oc, 'hideSiblings');
const spy2 = sinon.spy(oc, 'triggerHideEvent');
oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
spy.should.have.been.calledWith($sumiao);
spy2.should.have.been.called;
});
it('hEdgeClickHandler() accepts wrapped native edge payloads', function () {
const spy = sinon.spy(oc, 'hideSiblings');
oc.hEdgeClickHandler({
originalEvent: {
currentTarget: $sumiao,
target: query('.leftEdge', $sumiao)
}
});
spy.should.have.been.calledWith($sumiao);
spy.restore();
});
it('hEdgeClickHandler() restores the collapsed parent after topEdgeClickHandler()', function () {
oc.topEdgeClickHandler({ edge: query('.topEdge', $sumiao), node: $sumiao });
triggerEvent($bomiao, 'transitionend');
triggerEvent($laolao, 'transitionend');
oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
closestElement($sumiao, '.hierarchy').classList.contains('isAncestorsCollapsed').should.equal(false);
$laolao.classList.contains('hidden').should.equal(false);
$laolao.classList.contains('slide-down').should.equal(false);
$laolao.classList.contains('sliding').should.equal(true);
});
it('startLoading() and endLoading() accept a native edge node', function () {
const edgeEl = query('.bottomEdge', $sumiao);
oc.startLoading(edgeEl).should.be.true;
matchesSelector(edgeEl, '.hidden').should.be.true;
queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(1);
getState(oc.chart, 'inAjax').should.be.true;
oc.endLoading(edgeEl);
matchesSelector(edgeEl, '.hidden').should.be.false;
queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(0);
getState(oc.chart, 'inAjax').should.be.false;
});
it('startLoading() and endLoading() toggle native export buttons', function () {
const edgeEl = query('.bottomEdge', $sumiao);
const exportButtonEl = document.createElement('button');
exportButtonEl.className = 'oc-export-btn';
document.body.appendChild(exportButtonEl);
oc.startLoading(edgeEl);
exportButtonEl.disabled.should.equal(true);
oc.endLoading(edgeEl);
exportButtonEl.disabled.should.equal(false);
exportButtonEl.remove();
});
it('stopAjax() accepts a native node level element', function () {
const nodeLevelEl = oc.getChildren($sumiao)[0].closest('.nodes');
const spinnerEl = document.createElement('i');
spinnerEl.className = 'spinner';
nodeLevelEl.appendChild(spinnerEl);
setState(oc.chart, 'inAjax', true);
oc.stopAjax(nodeLevelEl);
getState(oc.chart, 'inAjax').should.be.false;
spinnerEl.remove();
});
it('backToLooseHandler() and backToCompactHandler() toggle compact mode classes', function () {
const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
$container.appendChild(compactNodeEl);
oc.backToLooseHandler({ compactNode: compactNodeEl });
matchesSelector(compactNodeEl, '.looseMode').should.be.true;
matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.true;
matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.false;
oc.backToCompactHandler({ compactNode: compactNodeEl });
matchesSelector(compactNodeEl, '.looseMode').should.be.false;
matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.true;
matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.false;
});
it('backToLooseHandler() and backToCompactHandler() accept wrapped native compact-node payloads', function () {
const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
$container.appendChild(compactNodeEl);
oc.back