UNPKG

orgchart

Version:

Simple and direct organization chart(tree-like hierarchy) library based on pure DOM.

333 lines (298 loc) 13 kB
const chai = require('chai'); const sinon = require('sinon'); const sinonChai = require('sinon-chai').default; const should = chai.should(); chai.use(sinonChai); require('jsdom-global')(); const OrgChart = require('../../src/js/orgchart'); 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; } describe('orgchart -- integration tests', function () { let container; const ds = { 'id': 'n1', 'name': 'Lao Lao', 'children': [ { 'id': 'n2', 'name': 'Bo Miao' }, { 'id': 'n3', 'name': 'Su Miao' } ] }; const fragment = '<div class="orgchart"><ul class="nodes"><li class="hierarchy">' + '<div id="n1" class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol">' + '</i>Lao Lao</div><i class="edge verticalEdge bottomEdge oci"></i></div><ul class="nodes">' + '<li class="hierarchy"><div id="n2" data-parent="n1" class="node">' + '<div class="title">Bo Miao</div><i class="edge verticalEdge topEdge oci"></i>' + '<i class="edge horizontalEdge rightEdge oci"></i><i class="edge horizontalEdge leftEdge oci">' + '</i></div></li><li class="hierarchy"><div id="n3" data-parent="n1" class="node">' + '<div class="title">Su Miao</div><i class="edge verticalEdge topEdge oci"></i>' + '<i class="edge horizontalEdge rightEdge oci"></i><i class="edge horizontalEdge leftEdge oci">' + '</i></div></li></ul></li></ul></div>'; let oc = {}; beforeEach(function () { document.body.innerHTML = '<div id="chart-container"></div>'; container = document.getElementById('chart-container'); }); afterEach(function () { container.innerHTML = ''; }); describe('init()', function () { it('initialize chart with json datasource', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds }); container.innerHTML.should.equal(fragment); }); it('initialize chart with <ul> datasource', function () { const ulElement = createElementFromHtml( '<ul id="ul-data">' + '<li data-id="n1">Lao Lao' + '<ul>' + '<li data-id="n2">Bo Miao</li>' + '<li data-id="n3">Su Miao</li>' + '</ul>' + '</li>' + '</ul>' ); document.body.appendChild(ulElement); oc = new OrgChart({ chartContainer: '#chart-container', 'data': ulElement }); container.innerHTML.should.equal(fragment); ulElement.remove(); }); it('initialize chart with the given visible level', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'visibleLevel': 1 }); query('.hierarchy', oc.chart).classList.contains('hidden').should.be.false; queryAll('.nodes', oc.chart)[1].classList.contains('hidden').should.be.true; queryAll('.node.slide-up', oc.chart).should.lengthOf(2); }); it('initialize chart with the given vertical level', function () { const fragment = '<li class="hierarchy"><div id="n1" class="node"><div class="title">' + '<i class="oci oci-menu parentNodeSymbol"></i>Lao Lao</div><i class="edge verticalEdge bottomEdge oci">' + '</i></div><ul class="nodes vertical"><li class="hierarchy"><div id="n2" data-parent="n1" class="node">' + '<div class="title">Bo Miao</div></div></li><li class="hierarchy"><div id="n3" data-parent="n1" class="node">' + '<div class="title">Su Miao</div></div></li></ul></li>'; oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'verticalLevel': 2 }); query('.nodes', oc.chart).innerHTML.should.equal(fragment); }); context('initialize chart with various combinations of "visibleLevel" and "verticalLevel" ', function () { const ds = { 'name': 'Lao Lao', 'children': [ { 'name': 'Bo Miao' }, { 'name': 'Su Miao', 'children': [ { 'name': 'Tie Hua' }, { 'name': 'Hei Hei' } ] } ] }; it('verticalLevel=2 and visibleLevel=1', function () { const fragment = '<div class="orgchart"><ul class="nodes"><li class="hierarchy">' + '<div class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol">' + '</i>Lao Lao</div><i class="edge verticalEdge bottomEdge oci"></i></div>' + '<ul class="nodes hidden vertical"><li class="hierarchy"><div class="node slide-up">' + '<div class="title">Bo Miao</div></div></li><li class="hierarchy">' + '<div class="node slide-up"><div class="title"><i class="oci oci-menu parentNodeSymbol"></i>Su Miao</div>' + '<i class="toggleBtn oci"></i></div><ul class="nodes hidden">' + '<li class="hierarchy"><div class="node slide-up"><div class="title">Tie Hua</div>' + '</div></li><li class="hierarchy"><div class="node slide-up">' + '<div class="title">Hei Hei</div></div></li></ul></li></ul></li></ul></div>'; oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'verticalLevel': 2, 'visibleLevel': 1 }); container.innerHTML.should.equal(fragment); }); it('verticalLevel=2 and visibleLevel=2', function () { const fragment = '<div class="orgchart"><ul class="nodes"><li class="hierarchy">' + '<div class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol"></i>Lao Lao</div>' + '<i class="edge verticalEdge bottomEdge oci"></i></div><ul class="nodes vertical">' + '<li class="hierarchy"><div class="node"><div class="title">Bo Miao</div></div></li>' + '<li class="hierarchy"><div class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol"></i>Su Miao</div>' + '<i class="toggleBtn oci"></i></div><ul class="nodes hidden">' + '<li class="hierarchy"><div class="node slide-up"><div class="title">Tie Hua</div></div>' + '</li><li class="hierarchy"><div class="node slide-up"><div class="title">Hei Hei</div>' + '</div></li></ul></li></ul></li></ul></div>'; oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'verticalLevel': 2, 'visibleLevel': 2 }); container.innerHTML.should.equal(fragment); }); it('verticalLevel=2 and visibleLevel=3', function () { const fragment = '<div class="orgchart"><ul class="nodes"><li class="hierarchy">' + '<div class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol"></i>Lao Lao</div>' + '<i class="edge verticalEdge bottomEdge oci"></i></div><ul class="nodes vertical">' + '<li class="hierarchy"><div class="node"><div class="title">Bo Miao</div></div></li>' + '<li class="hierarchy"><div class="node"><div class="title"><i class="oci oci-menu parentNodeSymbol"></i>Su Miao</div>' + '<i class="toggleBtn oci"></i></div><ul class="nodes">' + '<li class="hierarchy"><div class="node"><div class="title">Tie Hua</div></div>' + '</li><li class="hierarchy"><div class="node"><div class="title">Hei Hei</div></div>' + '</li></ul></li></ul></li></ul></div>'; oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'verticalLevel': 2, 'visibleLevel': 3 }); container.innerHTML.should.equal(fragment); }); }); it('initCompleted should be invoked immediately after construting one node', function () { const spy = sinon.spy(); oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'createNode': spy }); spy.should.have.been.callCount(3); spy.getCall(0).args[0].id.should.equal('n1'); spy.getCall(0).args[1].id.should.equal('n1'); spy.getCall(1).args[0].id.should.equal('n2'); spy.getCall(1).args[1].id.should.equal('n2'); spy.getCall(2).args[0].id.should.equal('n3'); spy.getCall(2).args[1].id.should.equal('n3'); // spy.should.always.have.been.calledOn(oc); }); it('renders custom data fields and invokes createNode after the node markup is built', function () { const createNode = sinon.spy(function (nodeElement, data) { nodeElement.setAttribute('data-role', data.role); }); oc = new OrgChart({ chartContainer: '#chart-container', data: { key: 'n1', label: 'Director', role: 'Operations', children: [{ key: 'n2', label: 'Engineer', role: 'Platform' }] }, nodeId: 'key', nodeTitle: 'label', nodeContent: 'role', createNode: createNode }); query('#n1 .title', oc.chart).textContent.should.equal('Director'); query('#n1 .content', oc.chart).textContent.should.equal('Operations'); query('#n2', oc.chart).getAttribute('data-role').should.equal('Platform'); createNode.should.have.been.calledTwice; createNode.firstCall.args[0].querySelector('.title').textContent.should.equal('Director'); }); it('renders custom node templates instead of the default title and content markup', function () { oc = new OrgChart({ chartContainer: '#chart-container', data: ds, nodeTemplate: function (data) { return '<section class="employee-card" data-name="' + data.name + '">' + data.name + '</section>'; } }); query('#n1 .employee-card', oc.chart).getAttribute('data-name').should.equal('Lao Lao'); query('#n2 .employee-card', oc.chart).textContent.should.equal('Bo Miao'); should.not.exist(query('#n1 > .title', oc.chart)); }); it('dispatches init.orgchart after initCompleted receives the rendered chart', function (done) { const initCompleted = sinon.spy(function (chartElement) { chartElement.addEventListener('init.orgchart', function () { initCompleted.should.have.been.calledOnce; initCompleted.firstCall.args[0].should.equal(chartElement); done(); }, { once: true }); }); oc = new OrgChart({ chartContainer: '#chart-container', data: ds, initCompleted: initCompleted }); }); it('initialize chart with default className', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'chartClass': 'demo' }); oc.chart.classList.contains('demo').should.be.true; }); it('initialize chart with export button', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'exportButton': true }); query('.oc-export-btn').outerHTML.should.equal('<button class="oc-export-btn">Export</button>'); }); it('initialize chart with "bottom to top" direction', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds, 'direction': 'b2t' }); oc.chart.classList.contains('b2t').should.be.true; }); it('applies horizontal direction classes', function () { oc = new OrgChart({ chartContainer: '#chart-container', data: ds, direction: 'l2r' }); oc.chart.classList.contains('l2r').should.be.true; oc.init({ direction: 'r2l' }); oc.chart.classList.contains('r2l').should.be.true; oc.chart.classList.contains('l2r').should.be.false; }); it('reinitialize chart with drggable feature', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds }); const spy = sinon.spy(oc, 'bindDragDrop'); oc.init({ 'draggable': true }); spy.should.have.been.callCount(3); spy.getCall(0).args[0].id.should.equal('n1'); spy.getCall(1).args[0].id.should.equal('n2'); spy.getCall(2).args[0].id.should.equal('n3'); }); it('reinitialize chart with pan feature', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds }); const spy = sinon.spy(oc, 'bindPan'); oc.init({ 'pan': true }); spy.should.have.been.callCount(1); }); it('reinitialize chart with zoom feature', function () { oc = new OrgChart({ chartContainer: '#chart-container', 'data': ds }); const spy = sinon.spy(oc, 'bindZoom'); oc.init({ 'zoom': true }); spy.should.have.been.callCount(1); }); }); });