orgchart 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +32 -0
- package/README.md +172 -93
- package/README.zh-cn.md +163 -74
- package/README.zh-tw.md +163 -74
- package/bower.json +6 -9
- package/demo/10000-nodes.html +139 -0
- package/demo/ajax-datasource.html +46 -45
- package/demo/bottom2top.html +12 -9
- package/demo/center-on-selected-node.html +21 -17
- package/demo/color-coded.html +12 -9
- package/demo/css/style.css +28 -0
- package/demo/custom-icons.html +12 -9
- package/demo/custom-template.html +12 -9
- package/demo/data-prop-compact.html +11 -8
- package/demo/data-prop-hybrid.html +79 -50
- package/demo/default-collapsed.html +12 -9
- package/demo/drag-drop-hybrid-chart.html +17 -14
- package/demo/drag-drop.html +21 -16
- package/demo/edit-chart.html +148 -94
- package/demo/edit-hybrid-chart.html +178 -135
- package/demo/expand-to-given-depth.html +30 -22
- package/demo/export-chart-pdf.html +12 -9
- package/demo/export-chart-with-pictures.html +19 -12
- package/demo/export-chart.html +12 -9
- package/demo/family-tree.html +14 -11
- package/demo/familytree-custom-properties.html +108 -0
- package/demo/filter-node.html +104 -57
- package/demo/get-hierarchy.html +17 -12
- package/demo/get-related-nodes.html +27 -17
- package/demo/index.html +5 -2
- package/demo/integrate-map.html +32 -27
- package/demo/left2right.html +12 -9
- package/demo/level-offset.html +16 -15
- package/demo/link-node.html +12 -9
- package/demo/local-datasource.html +11 -8
- package/demo/minimap.html +224 -0
- package/demo/nodes-of-different-widths.html +11 -8
- package/demo/ondemand-loading-data.html +88 -91
- package/demo/option-createNode.html +28 -17
- package/demo/pan-zoom.html +20 -11
- package/demo/reload-data.html +17 -14
- package/demo/report-path.html +59 -29
- package/demo/responsive-design.html +14 -11
- package/demo/right2left.html +12 -9
- package/demo/toggle-sibs-resp.html +12 -9
- package/demo/ul-datasource.html +11 -8
- package/demo/vertical-level.html +12 -9
- package/dist/css/{jquery.orgchart.css → orgchart.css} +143 -80
- package/dist/css/orgchart.min.css +1 -0
- package/dist/js/orgchart.js +4101 -0
- package/dist/js/orgchart.min.js +2 -0
- package/dist/js/orgchart.min.js.map +1 -0
- package/gulpfile.js +37 -26
- package/package.json +32 -32
- package/src/css/{jquery.orgchart.css → orgchart.css} +143 -80
- package/src/js/orgchart.js +4101 -0
- package/test/cypress/e2e/demo-interactions.cy.js +91 -0
- package/test/cypress/e2e/demo-rendering.cy.js +100 -0
- package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +22 -0
- package/test/cypress/e2e/edit-chart.cy.js +20 -0
- package/test/cypress/e2e/level-offset.cy.js +21 -0
- package/test/cypress/e2e/minimap.cy.js +66 -0
- package/test/cypress/e2e/pan-zoom.cy.js +165 -0
- package/test/cypress/e2e/report-path.cy.js +20 -0
- package/test/cypress/e2e/vertical-vevel.cy.js +5 -3
- package/test/e2e/local-datasource/test.js +1 -1
- package/test/integration/init-tests.js +168 -62
- package/test/integration/tests.js +1034 -56
- package/test/unit/lifecycle-builders-export.tests.js +1426 -0
- package/test/unit/minimap.tests.js +210 -0
- package/test/unit/tests.js +2372 -252
- package/dist/css/jquery.orgchart.min.css +0 -1
- package/dist/js/jquery.orgchart.js +0 -1741
- package/dist/js/jquery.orgchart.min.js +0 -5
- package/dist/js/jquery.orgchart.min.js.map +0 -1
- package/src/js/jquery.orgchart.js +0 -1741
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
const chai = require('chai');
|
|
2
|
+
const sinon = require('sinon');
|
|
3
|
+
const sinonChai = require('sinon-chai').default;
|
|
4
|
+
const should = chai.should();
|
|
5
|
+
|
|
6
|
+
chai.use(sinonChai);
|
|
7
|
+
require('jsdom-global')();
|
|
8
|
+
process.env.ORGCHART_TEST = '1';
|
|
9
|
+
|
|
10
|
+
const OrgChart = require('../../src/js/orgchart');
|
|
11
|
+
const getState = OrgChart.__testing__.getState;
|
|
12
|
+
const setState = OrgChart.__testing__.setState;
|
|
13
|
+
|
|
14
|
+
function query(selector, root) {
|
|
15
|
+
return (root || document).querySelector(selector);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function queryAll(selector, root) {
|
|
19
|
+
return Array.from((root || document).querySelectorAll(selector));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createElementFromHtml(html) {
|
|
23
|
+
const template = document.createElement('template');
|
|
24
|
+
|
|
25
|
+
template.innerHTML = html.trim();
|
|
26
|
+
return template.content.firstElementChild;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('orgchart -- init and builder unit tests', function () {
|
|
30
|
+
let $container;
|
|
31
|
+
let oc;
|
|
32
|
+
|
|
33
|
+
const ds = {
|
|
34
|
+
id: 'n1',
|
|
35
|
+
name: 'Lao Lao',
|
|
36
|
+
title: 'general manager',
|
|
37
|
+
children: [
|
|
38
|
+
{ id: 'n2', name: 'Bo Miao', title: 'department manager' },
|
|
39
|
+
{
|
|
40
|
+
id: 'n3',
|
|
41
|
+
name: 'Su Miao',
|
|
42
|
+
title: 'department manager',
|
|
43
|
+
children: [
|
|
44
|
+
{ id: 'n5', name: 'Tie Hua', title: 'senior engineer' },
|
|
45
|
+
{ id: 'n6', name: 'Hei Hei', title: 'senior engineer' }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{ id: 'n4', name: 'Hong Miao', title: 'department manager' }
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
beforeEach(function () {
|
|
53
|
+
document.body.innerHTML = '<div id="chart-host"><div id="chart-container"></div></div>';
|
|
54
|
+
$container = document.getElementById('chart-container');
|
|
55
|
+
oc = new OrgChart({
|
|
56
|
+
chartContainer: '#chart-container',
|
|
57
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
58
|
+
nodeContent: 'title'
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(function () {
|
|
63
|
+
document.body.innerHTML = '';
|
|
64
|
+
oc = null;
|
|
65
|
+
$container = null;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('constructor() and init()', function () {
|
|
69
|
+
it('supports invoking OrgChart without new', function () {
|
|
70
|
+
const chart = OrgChart({
|
|
71
|
+
chartContainer: '#chart-container',
|
|
72
|
+
data: JSON.parse(JSON.stringify(ds))
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
chart.should.be.instanceOf(OrgChart);
|
|
76
|
+
should.exist(chart.chart);
|
|
77
|
+
chart.chartContainer.should.equal($container);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('accepts the container alias in the options object', function () {
|
|
81
|
+
const chart = new OrgChart({
|
|
82
|
+
container: '#chart-container',
|
|
83
|
+
data: JSON.parse(JSON.stringify(ds))
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
chart.chartContainer.should.equal($container);
|
|
87
|
+
should.exist(chart.chart);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not initialize when the container cannot be resolved', function () {
|
|
91
|
+
const initSpy = sinon.spy(OrgChart.prototype, 'init');
|
|
92
|
+
const chart = new OrgChart({
|
|
93
|
+
chartContainer: '#missing-container',
|
|
94
|
+
data: JSON.parse(JSON.stringify(ds))
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
should.equal(chart.chartContainer, null);
|
|
98
|
+
should.equal(chart.chart, null);
|
|
99
|
+
initSpy.should.not.have.been.called;
|
|
100
|
+
|
|
101
|
+
initSpy.restore();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('stores merged options on the chart state during init', function () {
|
|
105
|
+
let chart;
|
|
106
|
+
|
|
107
|
+
$container.innerHTML = '';
|
|
108
|
+
chart = new OrgChart({
|
|
109
|
+
chartContainer: '#chart-container',
|
|
110
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
111
|
+
chartClass: 'alpha',
|
|
112
|
+
direction: 'l2r',
|
|
113
|
+
zoom: true,
|
|
114
|
+
visibleLevel: 3
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
getState(chart.chart, 'options').chartClass.should.equal('alpha');
|
|
118
|
+
getState(chart.chart, 'options').direction.should.equal('l2r');
|
|
119
|
+
getState(chart.chart, 'options').zoom.should.equal(true);
|
|
120
|
+
getState(chart.chart, 'options').visibleLevel.should.equal(3);
|
|
121
|
+
getState(chart.chart, 'options').nodeId.should.equal('id');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('replaces the previous chart element when init() runs again', function () {
|
|
125
|
+
const oldChart = oc.chart;
|
|
126
|
+
|
|
127
|
+
oc.init({ data: JSON.parse(JSON.stringify(ds)), chartClass: 'reloaded' });
|
|
128
|
+
|
|
129
|
+
should.exist(oc.chart);
|
|
130
|
+
oc.chart.should.not.equal(oldChart);
|
|
131
|
+
oldChart.isConnected.should.equal(false);
|
|
132
|
+
oc.chart.classList.contains('reloaded').should.equal(true);
|
|
133
|
+
$container.querySelectorAll('.orgchart').length.should.equal(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('applies chartClass and non-default direction classes during init', function () {
|
|
137
|
+
let chart;
|
|
138
|
+
|
|
139
|
+
$container.innerHTML = '';
|
|
140
|
+
chart = new OrgChart({
|
|
141
|
+
chartContainer: '#chart-container',
|
|
142
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
143
|
+
chartClass: 'custom-chart',
|
|
144
|
+
direction: 'b2t'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
chart.chart.classList.contains('custom-chart').should.equal(true);
|
|
148
|
+
chart.chart.classList.contains('b2t').should.equal(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('uses the root nodes container directly for grouped family-tree arrays', function () {
|
|
152
|
+
let chart;
|
|
153
|
+
|
|
154
|
+
$container.innerHTML = '';
|
|
155
|
+
chart = new OrgChart({
|
|
156
|
+
chartContainer: '#chart-container',
|
|
157
|
+
data: [[
|
|
158
|
+
{ id: 'n1', name: 'Lao Lao', relationship: '001' },
|
|
159
|
+
{ id: 'n2', name: 'Bo Miao', relationship: '001' }
|
|
160
|
+
]]
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
queryAll(':scope > ul.nodes > li.hierarchy', chart.chart).length.should.equal(1);
|
|
164
|
+
query(':scope > ul.nodes > li.hierarchy.spouse.couple', chart.chart).should.not.equal(null);
|
|
165
|
+
query('#n1', chart.chart).should.not.equal(null);
|
|
166
|
+
query('#n2', chart.chart).should.not.equal(null);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('builds a chart from a UL datasource during init()', function () {
|
|
170
|
+
let chart;
|
|
171
|
+
let ulElement;
|
|
172
|
+
|
|
173
|
+
$container.innerHTML = '';
|
|
174
|
+
ulElement = createElementFromHtml(
|
|
175
|
+
'<ul id="org-data">' +
|
|
176
|
+
'<li data-id="n1" data-title="chief">Lao Lao' +
|
|
177
|
+
'<ul>' +
|
|
178
|
+
'<li data-id="n2" data-title="manager">Bo Miao</li>' +
|
|
179
|
+
'<li data-id="n3" data-title="manager">Su Miao</li>' +
|
|
180
|
+
'</ul>' +
|
|
181
|
+
'</li>' +
|
|
182
|
+
'</ul>'
|
|
183
|
+
);
|
|
184
|
+
document.body.appendChild(ulElement);
|
|
185
|
+
chart = new OrgChart({
|
|
186
|
+
chartContainer: '#chart-container',
|
|
187
|
+
data: ulElement,
|
|
188
|
+
nodeContent: 'title'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
query('#n1', chart.chart).should.not.equal(null);
|
|
192
|
+
query('#n2', chart.chart).should.not.equal(null);
|
|
193
|
+
query('#n3', chart.chart).should.not.equal(null);
|
|
194
|
+
query('.content', query('#n1', chart.chart)).textContent.should.equal('chief');
|
|
195
|
+
|
|
196
|
+
ulElement.remove();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('routes plain object data through attachRel() before building', function () {
|
|
200
|
+
const attachRelSpy = sinon.spy(oc, 'attachRel');
|
|
201
|
+
|
|
202
|
+
oc.init({
|
|
203
|
+
data: {
|
|
204
|
+
id: 'r1',
|
|
205
|
+
name: 'Root',
|
|
206
|
+
children: [{ id: 'c1', name: 'Child' }]
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
attachRelSpy.should.have.been.called;
|
|
211
|
+
attachRelSpy.firstCall.args[1].should.equal('00');
|
|
212
|
+
attachRelSpy.restore();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('keeps explicit relationship data untouched during init()', function () {
|
|
216
|
+
const attachRelSpy = sinon.spy(oc, 'attachRel');
|
|
217
|
+
|
|
218
|
+
oc.init({
|
|
219
|
+
data: {
|
|
220
|
+
id: 'r1',
|
|
221
|
+
name: 'Root',
|
|
222
|
+
relationship: '001',
|
|
223
|
+
children: [{ id: 'c1', name: 'Child', relationship: '100' }]
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
attachRelSpy.should.not.have.been.called;
|
|
228
|
+
attachRelSpy.restore();
|
|
229
|
+
query('#r1', oc.chart).should.not.equal(null);
|
|
230
|
+
query('#c1', oc.chart).should.not.equal(null);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('calls attachExportButton() only when exportButton is enabled', function () {
|
|
234
|
+
const attachSpy = sinon.spy(oc, 'attachExportButton');
|
|
235
|
+
|
|
236
|
+
oc.init({ data: JSON.parse(JSON.stringify(ds)), exportButton: false });
|
|
237
|
+
attachSpy.should.not.have.been.called;
|
|
238
|
+
|
|
239
|
+
oc.init({ data: JSON.parse(JSON.stringify(ds)), exportButton: true });
|
|
240
|
+
attachSpy.should.have.been.calledOnce;
|
|
241
|
+
|
|
242
|
+
attachSpy.restore();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('binds pan and zoom during init() when the options are enabled', function () {
|
|
246
|
+
const panSpy = sinon.spy(oc, 'bindPan');
|
|
247
|
+
const zoomSpy = sinon.spy(oc, 'bindZoom');
|
|
248
|
+
|
|
249
|
+
oc.init({ data: JSON.parse(JSON.stringify(ds)), pan: true, zoom: true });
|
|
250
|
+
|
|
251
|
+
panSpy.should.have.been.calledOnce;
|
|
252
|
+
zoomSpy.should.have.been.calledOnce;
|
|
253
|
+
panSpy.restore();
|
|
254
|
+
zoomSpy.restore();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('setOptions()', function () {
|
|
259
|
+
it('binds pan when called with the string form and true', function () {
|
|
260
|
+
const bindSpy = sinon.spy(oc, 'bindPan');
|
|
261
|
+
|
|
262
|
+
oc.setOptions('pan', true).should.equal(oc);
|
|
263
|
+
|
|
264
|
+
bindSpy.should.have.been.calledOnce;
|
|
265
|
+
bindSpy.restore();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('unbinds pan when called with the string form and false', function () {
|
|
269
|
+
const unbindSpy = sinon.spy(oc, 'unbindPan');
|
|
270
|
+
|
|
271
|
+
oc.setOptions('pan', false).should.equal(oc);
|
|
272
|
+
|
|
273
|
+
unbindSpy.should.have.been.calledOnce;
|
|
274
|
+
unbindSpy.restore();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('binds zoom when called with the string form and true', function () {
|
|
278
|
+
const bindSpy = sinon.spy(oc, 'bindZoom');
|
|
279
|
+
|
|
280
|
+
oc.setOptions('zoom', true).should.equal(oc);
|
|
281
|
+
|
|
282
|
+
bindSpy.should.have.been.calledOnce;
|
|
283
|
+
bindSpy.restore();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('unbinds zoom when called with the string form and false', function () {
|
|
287
|
+
const unbindSpy = sinon.spy(oc, 'unbindZoom');
|
|
288
|
+
|
|
289
|
+
oc.setOptions('zoom', false).should.equal(oc);
|
|
290
|
+
|
|
291
|
+
unbindSpy.should.have.been.calledOnce;
|
|
292
|
+
unbindSpy.restore();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('reinitializes when called with an object that contains data', function () {
|
|
296
|
+
const initSpy = sinon.spy(oc, 'init');
|
|
297
|
+
|
|
298
|
+
oc.setOptions({ data: { id: 'x1', name: 'Reloaded Root' } }).should.equal(oc);
|
|
299
|
+
|
|
300
|
+
initSpy.should.have.been.calledOnce;
|
|
301
|
+
initSpy.firstCall.args[0].should.have.property('data');
|
|
302
|
+
initSpy.firstCall.args[0].data.id.should.equal('x1');
|
|
303
|
+
initSpy.firstCall.args[0].data.name.should.equal('Reloaded Root');
|
|
304
|
+
initSpy.firstCall.args[0].data.relationship.should.equal('000');
|
|
305
|
+
initSpy.firstCall.args[0].data.level.should.equal(1);
|
|
306
|
+
initSpy.restore();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('toggles pan and zoom independently when called with an object', function () {
|
|
310
|
+
const bindPanSpy = sinon.spy(oc, 'bindPan');
|
|
311
|
+
const unbindPanSpy = sinon.spy(oc, 'unbindPan');
|
|
312
|
+
const bindZoomSpy = sinon.spy(oc, 'bindZoom');
|
|
313
|
+
const unbindZoomSpy = sinon.spy(oc, 'unbindZoom');
|
|
314
|
+
|
|
315
|
+
oc.setOptions({ pan: true, zoom: false }).should.equal(oc);
|
|
316
|
+
bindPanSpy.should.have.been.calledOnce;
|
|
317
|
+
unbindZoomSpy.should.have.been.calledOnce;
|
|
318
|
+
unbindPanSpy.should.have.been.calledOnce;
|
|
319
|
+
bindZoomSpy.should.not.have.been.called;
|
|
320
|
+
|
|
321
|
+
bindPanSpy.restore();
|
|
322
|
+
unbindPanSpy.restore();
|
|
323
|
+
bindZoomSpy.restore();
|
|
324
|
+
unbindZoomSpy.restore();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('ignores unrelated option names in string form', function () {
|
|
328
|
+
const bindPanSpy = sinon.spy(oc, 'bindPan');
|
|
329
|
+
const bindZoomSpy = sinon.spy(oc, 'bindZoom');
|
|
330
|
+
|
|
331
|
+
oc.setOptions('visibleLevel', 2).should.equal(oc);
|
|
332
|
+
|
|
333
|
+
bindPanSpy.should.not.have.been.called;
|
|
334
|
+
bindZoomSpy.should.not.have.been.called;
|
|
335
|
+
bindPanSpy.restore();
|
|
336
|
+
bindZoomSpy.restore();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('attachRel()', function () {
|
|
341
|
+
it('assigns root and child relationship flags recursively', function () {
|
|
342
|
+
const tree = {
|
|
343
|
+
id: 'n1',
|
|
344
|
+
name: 'Root',
|
|
345
|
+
children: [
|
|
346
|
+
{ id: 'n2', name: 'Left' },
|
|
347
|
+
{ id: 'n3', name: 'Right', children: [{ id: 'n4', name: 'Leaf' }] }
|
|
348
|
+
]
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
oc.attachRel(tree, '00');
|
|
352
|
+
|
|
353
|
+
tree.relationship.should.equal('001');
|
|
354
|
+
tree.children[0].relationship.should.equal('110');
|
|
355
|
+
tree.children[1].relationship.should.equal('111');
|
|
356
|
+
tree.children[1].children[0].relationship.should.equal('100');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('marks compact nodes when the compact option predicate returns true', function () {
|
|
360
|
+
const chart = new OrgChart({
|
|
361
|
+
chartContainer: '#chart-container',
|
|
362
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
363
|
+
compact: function (nodeData) {
|
|
364
|
+
return nodeData.id === 'n3';
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
const tree = {
|
|
368
|
+
id: 'n1',
|
|
369
|
+
name: 'Root',
|
|
370
|
+
children: [
|
|
371
|
+
{ id: 'n2', name: 'Left' },
|
|
372
|
+
{
|
|
373
|
+
id: 'n3',
|
|
374
|
+
name: 'Right',
|
|
375
|
+
children: [{ id: 'n4', name: 'Leaf' }]
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
chart.attachRel(tree, '00');
|
|
381
|
+
|
|
382
|
+
should.equal(tree.compact, undefined);
|
|
383
|
+
tree.children[1].compact.should.equal(true);
|
|
384
|
+
tree.children[1].children[0].associatedCompact.should.equal(true);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('marks all descendants vertical under a hybrid node', function () {
|
|
388
|
+
const tree = {
|
|
389
|
+
id: 'n1',
|
|
390
|
+
name: 'Root',
|
|
391
|
+
hybrid: true,
|
|
392
|
+
children: [
|
|
393
|
+
{
|
|
394
|
+
id: 'n2',
|
|
395
|
+
name: 'Child',
|
|
396
|
+
children: [{ id: 'n3', name: 'Grandchild' }]
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
oc.attachRel(tree, '00');
|
|
402
|
+
|
|
403
|
+
tree.children[0].vertical.should.equal(true);
|
|
404
|
+
tree.children[0].children[0].vertical.should.equal(true);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('marks compact descendants and compact leaves differently', function () {
|
|
408
|
+
const tree = {
|
|
409
|
+
id: 'n1',
|
|
410
|
+
name: 'Root',
|
|
411
|
+
compact: true,
|
|
412
|
+
children: [
|
|
413
|
+
{
|
|
414
|
+
id: 'n2',
|
|
415
|
+
name: 'Has Children',
|
|
416
|
+
children: [{ id: 'n4', name: 'Leaf' }]
|
|
417
|
+
},
|
|
418
|
+
{ id: 'n3', name: 'Leaf' }
|
|
419
|
+
]
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
oc.attachRel(tree, '00');
|
|
423
|
+
|
|
424
|
+
tree.children[0].compact.should.equal(true);
|
|
425
|
+
tree.children[1].associatedCompact.should.equal(true);
|
|
426
|
+
tree.children[0].children[0].associatedCompact.should.equal(true);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('returns the same data object after mutating relationships', function () {
|
|
430
|
+
const tree = { id: 'n1', name: 'Root' };
|
|
431
|
+
|
|
432
|
+
oc.attachRel(tree, '00').should.equal(tree);
|
|
433
|
+
tree.relationship.should.equal('000');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('createNode()', function () {
|
|
438
|
+
it('renders the standard title, content, and edges from relationship flags', function () {
|
|
439
|
+
const nodeEl = oc.createNode({
|
|
440
|
+
id: 'n11',
|
|
441
|
+
name: 'Li Xin',
|
|
442
|
+
title: 'architect',
|
|
443
|
+
relationship: '111',
|
|
444
|
+
level: 1
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
nodeEl.classList.contains('node').should.equal(true);
|
|
448
|
+
nodeEl.id.should.equal('n11');
|
|
449
|
+
query('.title', nodeEl).textContent.trim().should.equal('Li Xin');
|
|
450
|
+
query('.content', nodeEl).textContent.should.equal('architect');
|
|
451
|
+
should.exist(query('.topEdge', nodeEl));
|
|
452
|
+
should.exist(query('.leftEdge', nodeEl));
|
|
453
|
+
should.exist(query('.rightEdge', nodeEl));
|
|
454
|
+
should.exist(query('.bottomEdge', nodeEl));
|
|
455
|
+
should.exist(query('.parentNodeSymbol', nodeEl));
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('propagates parentId to direct children when a node id exists', function () {
|
|
459
|
+
const data = {
|
|
460
|
+
id: 'n11',
|
|
461
|
+
name: 'Li Xin',
|
|
462
|
+
relationship: '001',
|
|
463
|
+
level: 0,
|
|
464
|
+
children: [{ id: 'n12', name: 'Child' }]
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
oc.createNode(data);
|
|
468
|
+
|
|
469
|
+
data.children[0].parentId.should.equal('n11');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('stores nodeData on the created element without the children array', function () {
|
|
473
|
+
const nodeEl = oc.createNode({
|
|
474
|
+
id: 'n11',
|
|
475
|
+
name: 'Li Xin',
|
|
476
|
+
relationship: '001',
|
|
477
|
+
level: 0,
|
|
478
|
+
children: [{ id: 'n12', name: 'Child' }]
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
getState(nodeEl, 'nodeData').id.should.equal('n11');
|
|
482
|
+
getState(nodeEl, 'nodeData').name.should.equal('Li Xin');
|
|
483
|
+
should.equal(getState(nodeEl, 'nodeData').children, undefined);
|
|
484
|
+
nodeEl.__ocNodeData.id.should.equal('n11');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('adds the slide-up class when the node level exceeds visibleLevel', function () {
|
|
488
|
+
const chart = new OrgChart({
|
|
489
|
+
chartContainer: '#chart-container',
|
|
490
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
491
|
+
visibleLevel: 1
|
|
492
|
+
});
|
|
493
|
+
const nodeEl = chart.createNode({
|
|
494
|
+
id: 'n11',
|
|
495
|
+
name: 'Li Xin',
|
|
496
|
+
relationship: '000',
|
|
497
|
+
level: 2
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
nodeEl.classList.contains('slide-up').should.equal(true);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('renders custom className and outsider classes', function () {
|
|
504
|
+
const nodeEl = oc.createNode({
|
|
505
|
+
id: 'n11',
|
|
506
|
+
name: 'Li Xin',
|
|
507
|
+
relationship: '000',
|
|
508
|
+
level: 0,
|
|
509
|
+
className: 'custom-node',
|
|
510
|
+
outsider: true
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
nodeEl.classList.contains('custom-node').should.equal(true);
|
|
514
|
+
nodeEl.classList.contains('outsider').should.equal(true);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('renders custom nodeTemplate output instead of the default title/content markup', function () {
|
|
518
|
+
const chart = new OrgChart({
|
|
519
|
+
chartContainer: '#chart-container',
|
|
520
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
521
|
+
nodeTemplate: function (data) {
|
|
522
|
+
return '<section class="custom-template">' + data.name + '</section>';
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
const nodeEl = chart.createNode({
|
|
526
|
+
id: 'n11',
|
|
527
|
+
name: 'Li Xin',
|
|
528
|
+
relationship: '001',
|
|
529
|
+
level: 0
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
should.exist(query('.custom-template', nodeEl));
|
|
533
|
+
should.equal(query('.title', nodeEl), null);
|
|
534
|
+
should.equal(query('.content', nodeEl), null);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('omits the content block when nodeContent is undefined', function () {
|
|
538
|
+
const chart = new OrgChart({
|
|
539
|
+
chartContainer: '#chart-container',
|
|
540
|
+
data: JSON.parse(JSON.stringify(ds))
|
|
541
|
+
});
|
|
542
|
+
const nodeEl = chart.createNode({
|
|
543
|
+
id: 'n11',
|
|
544
|
+
name: 'Li Xin',
|
|
545
|
+
relationship: '001',
|
|
546
|
+
level: 0,
|
|
547
|
+
title: 'architect'
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
should.equal(query('.content', nodeEl), null);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('renders a vertical toggle button at or beyond verticalLevel', function () {
|
|
554
|
+
const chart = new OrgChart({
|
|
555
|
+
chartContainer: '#chart-container',
|
|
556
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
557
|
+
verticalLevel: 2
|
|
558
|
+
});
|
|
559
|
+
const nodeEl = chart.createNode({
|
|
560
|
+
id: 'n11',
|
|
561
|
+
name: 'Li Xin',
|
|
562
|
+
relationship: '001',
|
|
563
|
+
level: 2
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
should.exist(query('.toggleBtn', nodeEl));
|
|
567
|
+
should.equal(query('.bottomEdge', nodeEl), null);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('renders a toggle button for explicitly vertical nodes', function () {
|
|
571
|
+
const nodeEl = oc.createNode({
|
|
572
|
+
id: 'n11',
|
|
573
|
+
name: 'Li Xin',
|
|
574
|
+
relationship: '001',
|
|
575
|
+
level: 0,
|
|
576
|
+
vertical: true
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
should.exist(query('.toggleBtn', nodeEl));
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('renders a bottom edge for hybrid nodes with children', function () {
|
|
583
|
+
const nodeEl = oc.createNode({
|
|
584
|
+
id: 'n11',
|
|
585
|
+
name: 'Li Xin',
|
|
586
|
+
relationship: '001',
|
|
587
|
+
level: 0,
|
|
588
|
+
hybrid: true
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
should.exist(query('.bottomEdge', nodeEl));
|
|
592
|
+
should.equal(query('.toggleBtn', nodeEl), null);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('renders compact mode controls and grid columns for compact nodes', function () {
|
|
596
|
+
const nodeEl = oc.createNode({
|
|
597
|
+
id: 'n11',
|
|
598
|
+
name: 'Li Xin',
|
|
599
|
+
relationship: '001',
|
|
600
|
+
level: 0,
|
|
601
|
+
compact: true,
|
|
602
|
+
children: [
|
|
603
|
+
{ id: 'n12', name: 'A' },
|
|
604
|
+
{ id: 'n13', name: 'B' },
|
|
605
|
+
{ id: 'n14', name: 'C' }
|
|
606
|
+
]
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
should.exist(query('.backToCompactSymbol', nodeEl));
|
|
610
|
+
should.exist(query('.backToLooseSymbol', nodeEl));
|
|
611
|
+
nodeEl.style.gridTemplateColumns.should.equal('repeat(2, auto)');
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it('suppresses default directional edges for associatedCompact nodes', function () {
|
|
615
|
+
const nodeEl = oc.createNode({
|
|
616
|
+
id: 'n11',
|
|
617
|
+
name: 'Li Xin',
|
|
618
|
+
relationship: '111',
|
|
619
|
+
level: 1,
|
|
620
|
+
associatedCompact: true
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
should.equal(query('.topEdge', nodeEl), null);
|
|
624
|
+
should.equal(query('.leftEdge', nodeEl), null);
|
|
625
|
+
should.equal(query('.rightEdge', nodeEl), null);
|
|
626
|
+
should.equal(query('.bottomEdge', nodeEl), null);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('marks a created node draggable and initializes touch drag state when draggable is enabled', function () {
|
|
630
|
+
const chart = new OrgChart({
|
|
631
|
+
chartContainer: '#chart-container',
|
|
632
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
633
|
+
draggable: true
|
|
634
|
+
});
|
|
635
|
+
const bindSpy = sinon.spy(chart, 'bindDragDrop');
|
|
636
|
+
const nodeEl = chart.createNode({
|
|
637
|
+
id: 'n11',
|
|
638
|
+
name: 'Li Xin',
|
|
639
|
+
relationship: '001',
|
|
640
|
+
level: 0
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
nodeEl.getAttribute('draggable').should.equal('true');
|
|
644
|
+
bindSpy.should.have.been.calledOnce;
|
|
645
|
+
bindSpy.firstCall.args[0].should.equal(nodeEl);
|
|
646
|
+
chart.touchHandled.should.equal(false);
|
|
647
|
+
chart.touchMoved.should.equal(false);
|
|
648
|
+
should.equal(chart.touchTargetNode, null);
|
|
649
|
+
bindSpy.restore();
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('calls the createNode callback with the native node element and data', function () {
|
|
653
|
+
const createNodeSpy = sinon.spy();
|
|
654
|
+
const chart = new OrgChart({
|
|
655
|
+
chartContainer: '#chart-container',
|
|
656
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
657
|
+
createNode: createNodeSpy
|
|
658
|
+
});
|
|
659
|
+
const nodeData = {
|
|
660
|
+
id: 'n11',
|
|
661
|
+
name: 'Li Xin',
|
|
662
|
+
relationship: '001',
|
|
663
|
+
level: 0
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
createNodeSpy.resetHistory();
|
|
667
|
+
const nodeEl = chart.createNode(nodeData);
|
|
668
|
+
|
|
669
|
+
createNodeSpy.should.have.been.calledOnce;
|
|
670
|
+
createNodeSpy.firstCall.args[0].should.equal(nodeEl);
|
|
671
|
+
createNodeSpy.firstCall.args[1].should.equal(nodeData);
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe('buildInferiorNodes()', function () {
|
|
676
|
+
it('creates a hidden vertical nodes layer at the vertical cutoff', function () {
|
|
677
|
+
const chart = new OrgChart({
|
|
678
|
+
chartContainer: '#chart-container',
|
|
679
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
680
|
+
verticalLevel: 2,
|
|
681
|
+
visibleLevel: 1
|
|
682
|
+
});
|
|
683
|
+
const hierarchyEl = document.createElement('li');
|
|
684
|
+
const nodeEl = chart.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 1 });
|
|
685
|
+
|
|
686
|
+
hierarchyEl.className = 'hierarchy';
|
|
687
|
+
hierarchyEl.appendChild(nodeEl);
|
|
688
|
+
chart.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
689
|
+
collapsed: true,
|
|
690
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
691
|
+
}, 1);
|
|
692
|
+
|
|
693
|
+
should.exist(query(':scope > .nodes.vertical.hidden', hierarchyEl));
|
|
694
|
+
should.exist(query('#n12', hierarchyEl));
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('creates a vertical layer for hybrid nodes outside an existing vertical context', function () {
|
|
698
|
+
const hierarchyEl = document.createElement('li');
|
|
699
|
+
const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
|
|
700
|
+
|
|
701
|
+
hierarchyEl.className = 'hierarchy';
|
|
702
|
+
hierarchyEl.appendChild(nodeEl);
|
|
703
|
+
oc.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
704
|
+
hybrid: true,
|
|
705
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
706
|
+
}, 0);
|
|
707
|
+
|
|
708
|
+
should.exist(query(':scope > .nodes.vertical', hierarchyEl));
|
|
709
|
+
should.exist(query('#n12', hierarchyEl));
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it('does not add a second vertical class when already inside a vertical branch', function () {
|
|
713
|
+
const verticalWrapper = document.createElement('ul');
|
|
714
|
+
const hierarchyEl = document.createElement('li');
|
|
715
|
+
const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 1 });
|
|
716
|
+
let nodesEl;
|
|
717
|
+
|
|
718
|
+
verticalWrapper.className = 'nodes vertical';
|
|
719
|
+
hierarchyEl.className = 'hierarchy';
|
|
720
|
+
hierarchyEl.appendChild(nodeEl);
|
|
721
|
+
verticalWrapper.appendChild(hierarchyEl);
|
|
722
|
+
$container.appendChild(verticalWrapper);
|
|
723
|
+
|
|
724
|
+
oc.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
725
|
+
hybrid: true,
|
|
726
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
727
|
+
}, 1);
|
|
728
|
+
nodesEl = query(':scope > .nodes', hierarchyEl);
|
|
729
|
+
|
|
730
|
+
nodesEl.classList.contains('vertical').should.equal(false);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('marks the hierarchy as children-collapsed for hidden horizontal children', function () {
|
|
734
|
+
const chart = new OrgChart({
|
|
735
|
+
chartContainer: '#chart-container',
|
|
736
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
737
|
+
visibleLevel: 0
|
|
738
|
+
});
|
|
739
|
+
const hierarchyEl = document.createElement('li');
|
|
740
|
+
const nodeEl = chart.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
|
|
741
|
+
|
|
742
|
+
hierarchyEl.className = 'hierarchy';
|
|
743
|
+
hierarchyEl.appendChild(nodeEl);
|
|
744
|
+
chart.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
745
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
746
|
+
}, 0);
|
|
747
|
+
|
|
748
|
+
hierarchyEl.classList.contains('isChildrenCollapsed').should.equal(true);
|
|
749
|
+
should.exist(query(':scope > .nodes.hidden', hierarchyEl));
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('keeps compact descendants inside the compact node element', function () {
|
|
753
|
+
const hierarchyEl = document.createElement('li');
|
|
754
|
+
const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
|
|
755
|
+
|
|
756
|
+
hierarchyEl.className = 'hierarchy';
|
|
757
|
+
hierarchyEl.appendChild(nodeEl);
|
|
758
|
+
oc.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
759
|
+
compact: true,
|
|
760
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
761
|
+
}, 0);
|
|
762
|
+
|
|
763
|
+
nodeEl.classList.contains('compact').should.equal(true);
|
|
764
|
+
should.equal(query('.nodes', hierarchyEl), null);
|
|
765
|
+
should.exist(query('#n12', nodeEl));
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it('handles grouped family-tree children arrays', function () {
|
|
769
|
+
const hierarchyEl = document.createElement('li');
|
|
770
|
+
const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
|
|
771
|
+
|
|
772
|
+
hierarchyEl.className = 'hierarchy';
|
|
773
|
+
hierarchyEl.appendChild(nodeEl);
|
|
774
|
+
oc.buildInferiorNodes(hierarchyEl, nodeEl, {
|
|
775
|
+
children: [[
|
|
776
|
+
{ id: 'n12', name: 'Spouse A', relationship: '001' },
|
|
777
|
+
{
|
|
778
|
+
id: 'n13',
|
|
779
|
+
name: 'Spouse B',
|
|
780
|
+
relationship: '001',
|
|
781
|
+
outsider: true,
|
|
782
|
+
children: [[{ id: 'n14', name: 'Child', relationship: '000' }]]
|
|
783
|
+
}
|
|
784
|
+
]]
|
|
785
|
+
}, 0);
|
|
786
|
+
|
|
787
|
+
should.exist(query('.couple', hierarchyEl));
|
|
788
|
+
should.exist(query('#n12', hierarchyEl));
|
|
789
|
+
should.exist(query('#n13', hierarchyEl));
|
|
790
|
+
should.exist(query('#n14', hierarchyEl));
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('returns early when the hierarchy element cannot be resolved', function () {
|
|
794
|
+
should.equal(oc.buildInferiorNodes(null, null, { children: [] }, 0), undefined);
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
describe('buildHierarchy()', function () {
|
|
799
|
+
it('creates a single node and its descendants for plain object data', function () {
|
|
800
|
+
const hierarchyEl = document.createElement('li');
|
|
801
|
+
|
|
802
|
+
hierarchyEl.className = 'hierarchy';
|
|
803
|
+
oc.buildHierarchy(hierarchyEl, {
|
|
804
|
+
id: 'n11',
|
|
805
|
+
name: 'Parent',
|
|
806
|
+
relationship: '001',
|
|
807
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
should.exist(query('#n11', hierarchyEl));
|
|
811
|
+
should.exist(query('#n12', hierarchyEl));
|
|
812
|
+
should.exist(query('.nodes', hierarchyEl));
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('derives the level from ancestor nodes layers when level is absent', function () {
|
|
816
|
+
const chartEl = document.createElement('div');
|
|
817
|
+
const outerNodesEl = document.createElement('ul');
|
|
818
|
+
const outerHierarchyEl = document.createElement('li');
|
|
819
|
+
const innerNodesEl = document.createElement('ul');
|
|
820
|
+
const hierarchyEl = document.createElement('li');
|
|
821
|
+
const data = { id: 'n11', name: 'Parent', relationship: '000' };
|
|
822
|
+
|
|
823
|
+
chartEl.className = 'orgchart';
|
|
824
|
+
outerNodesEl.className = 'nodes';
|
|
825
|
+
outerHierarchyEl.className = 'hierarchy';
|
|
826
|
+
innerNodesEl.className = 'nodes';
|
|
827
|
+
hierarchyEl.className = 'hierarchy';
|
|
828
|
+
outerHierarchyEl.appendChild(innerNodesEl);
|
|
829
|
+
innerNodesEl.appendChild(hierarchyEl);
|
|
830
|
+
outerNodesEl.appendChild(outerHierarchyEl);
|
|
831
|
+
chartEl.appendChild(outerNodesEl);
|
|
832
|
+
$container.appendChild(chartEl);
|
|
833
|
+
|
|
834
|
+
oc.buildHierarchy(hierarchyEl, data);
|
|
835
|
+
|
|
836
|
+
data.level.should.equal(2);
|
|
837
|
+
should.exist(query('#n11', hierarchyEl));
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('preserves an explicit data.level value', function () {
|
|
841
|
+
const hierarchyEl = document.createElement('li');
|
|
842
|
+
const data = { id: 'n11', name: 'Parent', relationship: '000', level: 7 };
|
|
843
|
+
|
|
844
|
+
hierarchyEl.className = 'hierarchy';
|
|
845
|
+
oc.buildHierarchy(hierarchyEl, data);
|
|
846
|
+
|
|
847
|
+
data.level.should.equal(7);
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('propagates a derived level into grouped family-tree arrays', function () {
|
|
851
|
+
const hierarchyEl = document.createElement('li');
|
|
852
|
+
const familyData = [[
|
|
853
|
+
{ id: 'n11', name: 'Spouse A', relationship: '001' },
|
|
854
|
+
{ id: 'n12', name: 'Spouse B', relationship: '001', outsider: true }
|
|
855
|
+
]];
|
|
856
|
+
|
|
857
|
+
hierarchyEl.className = 'hierarchy';
|
|
858
|
+
oc.buildHierarchy(hierarchyEl, familyData);
|
|
859
|
+
|
|
860
|
+
familyData[0][0].level.should.equal(0);
|
|
861
|
+
familyData[0][1].level.should.equal(0);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('creates spouse, couple, and insider wrappers for family trees', function () {
|
|
865
|
+
const hierarchyEl = document.createElement('li');
|
|
866
|
+
|
|
867
|
+
hierarchyEl.className = 'hierarchy';
|
|
868
|
+
oc.buildHierarchy(hierarchyEl, [[
|
|
869
|
+
{ id: 'n11', name: 'Spouse A', relationship: '001', outsider: false },
|
|
870
|
+
{ id: 'n12', name: 'Spouse B', relationship: '001', outsider: true },
|
|
871
|
+
{ id: 'n13', name: 'Spouse C', relationship: '001', outsider: true }
|
|
872
|
+
]]);
|
|
873
|
+
|
|
874
|
+
queryAll(':scope > .spouse', hierarchyEl).length.should.equal(3);
|
|
875
|
+
queryAll(':scope > .insider', hierarchyEl).length.should.equal(1);
|
|
876
|
+
should.exist(query('#n11', hierarchyEl));
|
|
877
|
+
should.exist(query('#n12', hierarchyEl));
|
|
878
|
+
should.exist(query('#n13', hierarchyEl));
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('inserts the second spouse after the first one for a couple wrapper', function () {
|
|
882
|
+
const hierarchyEl = document.createElement('li');
|
|
883
|
+
|
|
884
|
+
hierarchyEl.className = 'hierarchy';
|
|
885
|
+
oc.buildHierarchy(hierarchyEl, [[
|
|
886
|
+
{ id: 'n11', name: 'Spouse A', relationship: '001', outsider: false },
|
|
887
|
+
{ id: 'n12', name: 'Spouse B', relationship: '001', outsider: true }
|
|
888
|
+
]]);
|
|
889
|
+
|
|
890
|
+
queryAll('.node', hierarchyEl).map(function (nodeEl) {
|
|
891
|
+
return nodeEl.id;
|
|
892
|
+
}).should.deep.equal(['n11', 'n12']);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it('creates descendant nodes when data only contains children metadata', function () {
|
|
896
|
+
const hierarchyEl = document.createElement('li');
|
|
897
|
+
|
|
898
|
+
hierarchyEl.className = 'hierarchy';
|
|
899
|
+
oc.buildHierarchy(hierarchyEl, {
|
|
900
|
+
children: [{ id: 'n12', name: 'Child', relationship: '000' }]
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
should.equal(query('.node', hierarchyEl).id, 'n12');
|
|
904
|
+
should.exist(query('#n12', hierarchyEl));
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it('returns early when the hierarchy element cannot be resolved', function () {
|
|
908
|
+
should.equal(oc.buildHierarchy(null, { id: 'n11', name: 'Parent' }), undefined);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
describe('buildChildNode()', function () {
|
|
913
|
+
it('returns early when the append target is missing', function () {
|
|
914
|
+
should.equal(oc.buildChildNode(null, [{ id: 'n11', name: 'Child' }]), undefined);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
it('delegates to buildHierarchy() when the parent has no child nodes container', function () {
|
|
918
|
+
const hierarchyEl = document.createElement('li');
|
|
919
|
+
const buildSpy = sinon.spy(oc, 'buildHierarchy');
|
|
920
|
+
|
|
921
|
+
hierarchyEl.className = 'hierarchy';
|
|
922
|
+
oc.buildChildNode(hierarchyEl, [{ id: 'n11', name: 'Child' }]);
|
|
923
|
+
|
|
924
|
+
buildSpy.should.have.callCount(2);
|
|
925
|
+
buildSpy.firstCall.args[0].should.equal(hierarchyEl);
|
|
926
|
+
buildSpy.firstCall.args[1].should.have.property('children');
|
|
927
|
+
buildSpy.secondCall.args[1].id.should.equal('n11');
|
|
928
|
+
buildSpy.restore();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it('appends new child hierarchies into an existing child nodes container', function () {
|
|
932
|
+
const hierarchyEl = document.createElement('li');
|
|
933
|
+
const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
|
|
934
|
+
const nodesEl = document.createElement('ul');
|
|
935
|
+
|
|
936
|
+
hierarchyEl.className = 'hierarchy';
|
|
937
|
+
nodesEl.className = 'nodes';
|
|
938
|
+
hierarchyEl.appendChild(nodeEl);
|
|
939
|
+
hierarchyEl.appendChild(nodesEl);
|
|
940
|
+
oc.buildChildNode(hierarchyEl, [
|
|
941
|
+
{ id: 'n12', name: 'Child A', relationship: '000' },
|
|
942
|
+
{ id: 'n13', name: 'Child B', relationship: '000' }
|
|
943
|
+
]);
|
|
944
|
+
|
|
945
|
+
queryAll(':scope > .hierarchy', nodesEl).length.should.equal(2);
|
|
946
|
+
should.exist(query('#n12', nodesEl));
|
|
947
|
+
should.exist(query('#n13', nodesEl));
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
describe('buildParentNode()', function () {
|
|
952
|
+
it('returns early when the current root cannot be resolved', function () {
|
|
953
|
+
should.equal(oc.buildParentNode(null, { id: 'n0', name: 'Ancestor' }), undefined);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it('wraps the current root list with a new ancestor hierarchy', function () {
|
|
957
|
+
const currentRootEl = query('#n1', oc.chart);
|
|
958
|
+
|
|
959
|
+
oc.buildParentNode(currentRootEl, { id: 'n0', name: 'Ancestor' });
|
|
960
|
+
|
|
961
|
+
should.exist(query('#n0', oc.chart));
|
|
962
|
+
query('#n1', oc.chart).closest('.nodes').previousElementSibling.id.should.equal('n0');
|
|
963
|
+
query(':scope > .nodes > .hierarchy > #n0', oc.chart).should.not.equal(null);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('defaults the new parent relationship to 001 when absent', function () {
|
|
967
|
+
const currentRootEl = query('#n1', oc.chart);
|
|
968
|
+
|
|
969
|
+
oc.buildParentNode(currentRootEl, { id: 'n0', name: 'Ancestor' });
|
|
970
|
+
|
|
971
|
+
getState(query('#n0', oc.chart), 'nodeData').relationship.should.equal('001');
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
describe('attachExportButton()', function () {
|
|
976
|
+
it('returns early when the chart container has no parent element', function () {
|
|
977
|
+
const detachedContainer = document.createElement('div');
|
|
978
|
+
const chart = new OrgChart({
|
|
979
|
+
data: JSON.parse(JSON.stringify(ds))
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
chart.chartContainer = detachedContainer;
|
|
983
|
+
chart.options = Object.assign({}, chart.defaultOptions);
|
|
984
|
+
|
|
985
|
+
should.equal(chart.attachExportButton(), undefined);
|
|
986
|
+
should.equal(document.querySelector('.oc-export-btn'), null);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
it('uses exportButtonName as the rendered button text', function () {
|
|
990
|
+
let chart;
|
|
991
|
+
|
|
992
|
+
$container.innerHTML = '';
|
|
993
|
+
chart = new OrgChart({
|
|
994
|
+
chartContainer: '#chart-container',
|
|
995
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
996
|
+
exportButton: true,
|
|
997
|
+
exportButtonName: 'Download Chart'
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
document.querySelector('.oc-export-btn').textContent.should.equal('Download Chart');
|
|
1001
|
+
document.querySelector('.oc-export-btn').remove();
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
describe('export helpers', function () {
|
|
1006
|
+
it('exportPDF() uses the jsPDF constructor for wide canvases', function () {
|
|
1007
|
+
const originalJsPDF = global.jsPDF;
|
|
1008
|
+
const originalWindowJsPDF = window.jsPDF;
|
|
1009
|
+
const addImageSpy = sinon.spy();
|
|
1010
|
+
const saveSpy = sinon.spy();
|
|
1011
|
+
const jsPDFSpy = sinon.spy(function () {
|
|
1012
|
+
this.addImage = addImageSpy;
|
|
1013
|
+
this.save = saveSpy;
|
|
1014
|
+
});
|
|
1015
|
+
const canvas = {
|
|
1016
|
+
width: 500,
|
|
1017
|
+
height: 200,
|
|
1018
|
+
toDataURL: function () {
|
|
1019
|
+
return 'data:image/png;base64,fake';
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
global.jsPDF = jsPDFSpy;
|
|
1024
|
+
window.jsPDF = jsPDFSpy;
|
|
1025
|
+
|
|
1026
|
+
oc.exportPDF(canvas, 'WideChart');
|
|
1027
|
+
|
|
1028
|
+
jsPDFSpy.should.have.been.calledOnce;
|
|
1029
|
+
jsPDFSpy.firstCall.args[0].orientation.should.equal('landscape');
|
|
1030
|
+
jsPDFSpy.firstCall.args[0].format.should.deep.equal([500, 200]);
|
|
1031
|
+
addImageSpy.should.have.been.calledOnce;
|
|
1032
|
+
addImageSpy.should.have.been.calledWith('data:image/png;base64,fake', 'png', 0, 0, 500, 200);
|
|
1033
|
+
saveSpy.should.have.been.calledOnce;
|
|
1034
|
+
saveSpy.firstCall.args[0].should.equal('WideChart.pdf');
|
|
1035
|
+
|
|
1036
|
+
global.jsPDF = originalJsPDF;
|
|
1037
|
+
window.jsPDF = originalWindowJsPDF;
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
it('exportPDF() uses portrait for tall canvases', function () {
|
|
1041
|
+
const originalJsPDF = global.jsPDF;
|
|
1042
|
+
const originalWindowJsPDF = window.jsPDF;
|
|
1043
|
+
const addImageSpy = sinon.spy();
|
|
1044
|
+
const saveSpy = sinon.spy();
|
|
1045
|
+
const jsPDFSpy = sinon.spy(function () {
|
|
1046
|
+
this.addImage = addImageSpy;
|
|
1047
|
+
this.save = saveSpy;
|
|
1048
|
+
});
|
|
1049
|
+
const canvas = {
|
|
1050
|
+
width: 200,
|
|
1051
|
+
height: 500,
|
|
1052
|
+
toDataURL: function () {
|
|
1053
|
+
return 'data:image/png;base64,fake';
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
global.jsPDF = jsPDFSpy;
|
|
1058
|
+
window.jsPDF = jsPDFSpy;
|
|
1059
|
+
|
|
1060
|
+
oc.exportPDF(canvas, 'TallChart');
|
|
1061
|
+
|
|
1062
|
+
jsPDFSpy.should.have.been.calledOnce;
|
|
1063
|
+
jsPDFSpy.firstCall.args[0].orientation.should.equal('portrait');
|
|
1064
|
+
jsPDFSpy.firstCall.args[0].format.should.deep.equal([500, 200]);
|
|
1065
|
+
addImageSpy.should.have.been.calledOnce;
|
|
1066
|
+
addImageSpy.should.have.been.calledWith('data:image/png;base64,fake', 'png', 0, 0, 200, 500);
|
|
1067
|
+
saveSpy.should.have.been.calledOnce;
|
|
1068
|
+
|
|
1069
|
+
global.jsPDF = originalJsPDF;
|
|
1070
|
+
window.jsPDF = originalWindowJsPDF;
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
it('exportPDF() preserves logical dimensions for high-resolution canvases', function () {
|
|
1074
|
+
const originalJsPDF = global.jsPDF;
|
|
1075
|
+
const originalWindowJsPDF = window.jsPDF;
|
|
1076
|
+
const addImageSpy = sinon.spy();
|
|
1077
|
+
const jsPDFSpy = sinon.spy(function () {
|
|
1078
|
+
this.addImage = addImageSpy;
|
|
1079
|
+
this.save = sinon.spy();
|
|
1080
|
+
});
|
|
1081
|
+
const canvas = {
|
|
1082
|
+
width: 1000,
|
|
1083
|
+
height: 400,
|
|
1084
|
+
toDataURL: function () {
|
|
1085
|
+
return 'data:image/png;base64,fake';
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
global.jsPDF = jsPDFSpy;
|
|
1090
|
+
window.jsPDF = jsPDFSpy;
|
|
1091
|
+
|
|
1092
|
+
oc.exportPDF(canvas, 'HighResolutionChart', 2);
|
|
1093
|
+
|
|
1094
|
+
jsPDFSpy.firstCall.args[0].format.should.deep.equal([500, 200]);
|
|
1095
|
+
addImageSpy.should.have.been.calledWith('data:image/png;base64,fake', 'png', 0, 0, 500, 200);
|
|
1096
|
+
|
|
1097
|
+
global.jsPDF = originalJsPDF;
|
|
1098
|
+
window.jsPDF = originalWindowJsPDF;
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
it('exportPNG() returns early when the chart container is missing', function () {
|
|
1102
|
+
const chart = new OrgChart({ data: JSON.parse(JSON.stringify(ds)) });
|
|
1103
|
+
|
|
1104
|
+
chart.chartContainer = null;
|
|
1105
|
+
should.equal(chart.exportPNG({ toDataURL: function () { return ''; } }, 'Missing'), undefined);
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
it('exportPNG() uses msSaveBlob on Edge and IE code paths', function () {
|
|
1109
|
+
const originalNavigator = window.navigator;
|
|
1110
|
+
const canvas = {
|
|
1111
|
+
msToBlob: sinon.stub().returns('blob-data')
|
|
1112
|
+
};
|
|
1113
|
+
const msSaveBlobSpy = sinon.spy();
|
|
1114
|
+
|
|
1115
|
+
Object.defineProperty(window, 'navigator', {
|
|
1116
|
+
configurable: true,
|
|
1117
|
+
value: {
|
|
1118
|
+
appName: 'Microsoft Internet Explorer',
|
|
1119
|
+
appVersion: '11.0',
|
|
1120
|
+
msSaveBlob: msSaveBlobSpy
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
oc.exportPNG(canvas, 'LegacyExport');
|
|
1125
|
+
|
|
1126
|
+
canvas.msToBlob.should.have.been.calledOnce;
|
|
1127
|
+
msSaveBlobSpy.should.have.been.calledOnce;
|
|
1128
|
+
msSaveBlobSpy.firstCall.args[1].should.equal('LegacyExport.png');
|
|
1129
|
+
|
|
1130
|
+
Object.defineProperty(window, 'navigator', {
|
|
1131
|
+
configurable: true,
|
|
1132
|
+
value: originalNavigator
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it('exportPNG() creates and reuses a chartClass-scoped download anchor', function () {
|
|
1137
|
+
const originalNavigator = window.navigator;
|
|
1138
|
+
const originalClick = window.HTMLAnchorElement.prototype.click;
|
|
1139
|
+
const originalSidebar = window.sidebar;
|
|
1140
|
+
const originalWebkitAppearance = document.documentElement.style.WebkitAppearance;
|
|
1141
|
+
const canvas = {
|
|
1142
|
+
toDataURL: function () {
|
|
1143
|
+
return 'data:image/png;base64,fake';
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
const clickSpy = sinon.spy();
|
|
1147
|
+
let chart;
|
|
1148
|
+
|
|
1149
|
+
$container.innerHTML = '';
|
|
1150
|
+
chart = new OrgChart({
|
|
1151
|
+
chartContainer: '#chart-container',
|
|
1152
|
+
data: JSON.parse(JSON.stringify(ds)),
|
|
1153
|
+
chartClass: 'blue'
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
Object.defineProperty(window, 'navigator', {
|
|
1157
|
+
configurable: true,
|
|
1158
|
+
value: {
|
|
1159
|
+
appName: 'Netscape',
|
|
1160
|
+
appVersion: 'Chrome 123'
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
Object.defineProperty(window, 'sidebar', {
|
|
1164
|
+
configurable: true,
|
|
1165
|
+
value: false
|
|
1166
|
+
});
|
|
1167
|
+
document.documentElement.style.WebkitAppearance = 'none';
|
|
1168
|
+
window.HTMLAnchorElement.prototype.click = clickSpy;
|
|
1169
|
+
|
|
1170
|
+
chart.exportPNG(canvas, 'ChartOne');
|
|
1171
|
+
chart.exportPNG(canvas, 'ChartTwo');
|
|
1172
|
+
|
|
1173
|
+
queryAll('.download-btn.blue', chart.chartContainer).length.should.equal(1);
|
|
1174
|
+
query('.download-btn.blue', chart.chartContainer).getAttribute('download').should.equal('ChartTwo.png');
|
|
1175
|
+
clickSpy.callCount.should.equal(2);
|
|
1176
|
+
|
|
1177
|
+
Object.defineProperty(window, 'navigator', {
|
|
1178
|
+
configurable: true,
|
|
1179
|
+
value: originalNavigator
|
|
1180
|
+
});
|
|
1181
|
+
Object.defineProperty(window, 'sidebar', {
|
|
1182
|
+
configurable: true,
|
|
1183
|
+
value: originalSidebar
|
|
1184
|
+
});
|
|
1185
|
+
document.documentElement.style.WebkitAppearance = originalWebkitAppearance;
|
|
1186
|
+
window.HTMLAnchorElement.prototype.click = originalClick;
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it('export() returns false when there is no chart container', function () {
|
|
1190
|
+
const chart = new OrgChart({ data: JSON.parse(JSON.stringify(ds)) });
|
|
1191
|
+
|
|
1192
|
+
chart.chartContainer = null;
|
|
1193
|
+
chart.options = Object.assign({}, chart.defaultOptions);
|
|
1194
|
+
chart.export('MissingChart').should.equal(false);
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it('export() creates a mask when one is missing and routes png exports to exportPNG()', function (done) {
|
|
1198
|
+
const originalHtml2canvas = global.html2canvas;
|
|
1199
|
+
const originalWindowHtml2canvas = window.html2canvas;
|
|
1200
|
+
const exportPNGStub = sinon.stub(oc, 'exportPNG');
|
|
1201
|
+
const fakeCanvas = {
|
|
1202
|
+
toDataURL: function () {
|
|
1203
|
+
return 'data:image/png;base64,fake';
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
global.html2canvas = function (element, options) {
|
|
1208
|
+
try {
|
|
1209
|
+
element.should.equal(oc.chart);
|
|
1210
|
+
options.width.should.equal(oc.chart.clientWidth);
|
|
1211
|
+
options.height.should.equal(oc.chart.clientHeight);
|
|
1212
|
+
options.scale.should.equal(2);
|
|
1213
|
+
options.useCORS.should.equal(true);
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
global.html2canvas = originalHtml2canvas;
|
|
1216
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1217
|
+
exportPNGStub.restore();
|
|
1218
|
+
done(error);
|
|
1219
|
+
return { then: function () {} };
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
return {
|
|
1223
|
+
then: function (resolve) {
|
|
1224
|
+
resolve(fakeCanvas);
|
|
1225
|
+
return { then: function () {} };
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
};
|
|
1229
|
+
window.html2canvas = global.html2canvas;
|
|
1230
|
+
|
|
1231
|
+
oc.export('UnitExport');
|
|
1232
|
+
|
|
1233
|
+
setTimeout(function () {
|
|
1234
|
+
try {
|
|
1235
|
+
exportPNGStub.should.have.been.calledOnce;
|
|
1236
|
+
exportPNGStub.firstCall.args[0].should.equal(fakeCanvas);
|
|
1237
|
+
exportPNGStub.firstCall.args[1].should.equal('UnitExport');
|
|
1238
|
+
query('.mask', $container).classList.contains('hidden').should.equal(true);
|
|
1239
|
+
$container.classList.contains('canvasContainer').should.equal(false);
|
|
1240
|
+
|
|
1241
|
+
global.html2canvas = originalHtml2canvas;
|
|
1242
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1243
|
+
exportPNGStub.restore();
|
|
1244
|
+
done();
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
global.html2canvas = originalHtml2canvas;
|
|
1247
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1248
|
+
exportPNGStub.restore();
|
|
1249
|
+
done(error);
|
|
1250
|
+
}
|
|
1251
|
+
}, 0);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it('export() reuses an existing mask by unhiding it', function (done) {
|
|
1255
|
+
const originalHtml2canvas = global.html2canvas;
|
|
1256
|
+
const originalWindowHtml2canvas = window.html2canvas;
|
|
1257
|
+
const exportPNGStub = sinon.stub(oc, 'exportPNG');
|
|
1258
|
+
const maskEl = document.createElement('div');
|
|
1259
|
+
const fakeCanvas = { toDataURL: function () { return 'data:image/png;base64,fake'; } };
|
|
1260
|
+
|
|
1261
|
+
maskEl.className = 'mask hidden';
|
|
1262
|
+
$container.appendChild(maskEl);
|
|
1263
|
+
global.html2canvas = function () {
|
|
1264
|
+
return {
|
|
1265
|
+
then: function (resolve) {
|
|
1266
|
+
resolve(fakeCanvas);
|
|
1267
|
+
return { then: function () {} };
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
};
|
|
1271
|
+
window.html2canvas = global.html2canvas;
|
|
1272
|
+
|
|
1273
|
+
oc.export('ReuseMask');
|
|
1274
|
+
|
|
1275
|
+
setTimeout(function () {
|
|
1276
|
+
try {
|
|
1277
|
+
maskEl.classList.contains('hidden').should.equal(true);
|
|
1278
|
+
exportPNGStub.should.have.been.calledOnce;
|
|
1279
|
+
|
|
1280
|
+
global.html2canvas = originalHtml2canvas;
|
|
1281
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1282
|
+
exportPNGStub.restore();
|
|
1283
|
+
done();
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
global.html2canvas = originalHtml2canvas;
|
|
1286
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1287
|
+
exportPNGStub.restore();
|
|
1288
|
+
done(error);
|
|
1289
|
+
}
|
|
1290
|
+
}, 0);
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
it('export() swaps width and height for horizontal charts', function (done) {
|
|
1294
|
+
const originalHtml2canvas = global.html2canvas;
|
|
1295
|
+
const originalWindowHtml2canvas = window.html2canvas;
|
|
1296
|
+
const exportPNGStub = sinon.stub(oc, 'exportPNG');
|
|
1297
|
+
const fakeCanvas = { toDataURL: function () { return 'data:image/png;base64,fake'; } };
|
|
1298
|
+
|
|
1299
|
+
Object.defineProperty(oc.chart, 'clientWidth', { configurable: true, value: 300 });
|
|
1300
|
+
Object.defineProperty(oc.chart, 'clientHeight', { configurable: true, value: 120 });
|
|
1301
|
+
oc.options.direction = 'l2r';
|
|
1302
|
+
global.html2canvas = function (element, options) {
|
|
1303
|
+
try {
|
|
1304
|
+
options.width.should.equal(120);
|
|
1305
|
+
options.height.should.equal(300);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
global.html2canvas = originalHtml2canvas;
|
|
1308
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1309
|
+
exportPNGStub.restore();
|
|
1310
|
+
done(error);
|
|
1311
|
+
return { then: function () {} };
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
return {
|
|
1315
|
+
then: function (resolve) {
|
|
1316
|
+
resolve(fakeCanvas);
|
|
1317
|
+
return { then: function () {} };
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
};
|
|
1321
|
+
window.html2canvas = global.html2canvas;
|
|
1322
|
+
|
|
1323
|
+
oc.export('Horizontal');
|
|
1324
|
+
|
|
1325
|
+
setTimeout(function () {
|
|
1326
|
+
try {
|
|
1327
|
+
exportPNGStub.should.have.been.calledOnce;
|
|
1328
|
+
|
|
1329
|
+
global.html2canvas = originalHtml2canvas;
|
|
1330
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1331
|
+
exportPNGStub.restore();
|
|
1332
|
+
done();
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
global.html2canvas = originalHtml2canvas;
|
|
1335
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1336
|
+
exportPNGStub.restore();
|
|
1337
|
+
done(error);
|
|
1338
|
+
}
|
|
1339
|
+
}, 0);
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
it('export() clears cloned chart transforms inside onclone()', function (done) {
|
|
1343
|
+
const originalHtml2canvas = global.html2canvas;
|
|
1344
|
+
const originalWindowHtml2canvas = window.html2canvas;
|
|
1345
|
+
const exportPNGStub = sinon.stub(oc, 'exportPNG');
|
|
1346
|
+
const fakeCanvas = { toDataURL: function () { return 'data:image/png;base64,fake'; } };
|
|
1347
|
+
const clonedContainer = document.createElement('div');
|
|
1348
|
+
const clonedChart = document.createElement('div');
|
|
1349
|
+
|
|
1350
|
+
clonedContainer.className = 'canvasContainer';
|
|
1351
|
+
clonedChart.className = 'orgchart';
|
|
1352
|
+
clonedChart.style.transform = 'matrix(1, 0, 0, 1, 10, 20)';
|
|
1353
|
+
clonedContainer.appendChild(clonedChart);
|
|
1354
|
+
|
|
1355
|
+
global.html2canvas = function (element, options) {
|
|
1356
|
+
options.onclone({ querySelector: function (selector) {
|
|
1357
|
+
return selector === '.canvasContainer' ? clonedContainer : null;
|
|
1358
|
+
} });
|
|
1359
|
+
return {
|
|
1360
|
+
then: function (resolve) {
|
|
1361
|
+
resolve(fakeCanvas);
|
|
1362
|
+
return { then: function () {} };
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
};
|
|
1366
|
+
window.html2canvas = global.html2canvas;
|
|
1367
|
+
|
|
1368
|
+
oc.export('CloneCleanup');
|
|
1369
|
+
|
|
1370
|
+
setTimeout(function () {
|
|
1371
|
+
try {
|
|
1372
|
+
clonedContainer.style.overflow.should.equal('visible');
|
|
1373
|
+
clonedChart.style.transform.should.equal('');
|
|
1374
|
+
exportPNGStub.should.have.been.calledOnce;
|
|
1375
|
+
|
|
1376
|
+
global.html2canvas = originalHtml2canvas;
|
|
1377
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1378
|
+
exportPNGStub.restore();
|
|
1379
|
+
done();
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
global.html2canvas = originalHtml2canvas;
|
|
1382
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1383
|
+
exportPNGStub.restore();
|
|
1384
|
+
done(error);
|
|
1385
|
+
}
|
|
1386
|
+
}, 0);
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
it('export() routes pdf exports to exportPDF()', function (done) {
|
|
1390
|
+
const originalHtml2canvas = global.html2canvas;
|
|
1391
|
+
const originalWindowHtml2canvas = window.html2canvas;
|
|
1392
|
+
const exportPDFStub = sinon.stub(oc, 'exportPDF');
|
|
1393
|
+
const fakeCanvas = { toDataURL: function () { return 'data:image/png;base64,fake'; } };
|
|
1394
|
+
|
|
1395
|
+
global.html2canvas = function () {
|
|
1396
|
+
return {
|
|
1397
|
+
then: function (resolve) {
|
|
1398
|
+
resolve(fakeCanvas);
|
|
1399
|
+
return { then: function () {} };
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
};
|
|
1403
|
+
window.html2canvas = global.html2canvas;
|
|
1404
|
+
|
|
1405
|
+
oc.export('PdfExport', 'pdf');
|
|
1406
|
+
|
|
1407
|
+
setTimeout(function () {
|
|
1408
|
+
try {
|
|
1409
|
+
exportPDFStub.should.have.been.calledOnce;
|
|
1410
|
+
exportPDFStub.firstCall.args[0].should.equal(fakeCanvas);
|
|
1411
|
+
exportPDFStub.firstCall.args[1].should.equal('PdfExport');
|
|
1412
|
+
|
|
1413
|
+
global.html2canvas = originalHtml2canvas;
|
|
1414
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1415
|
+
exportPDFStub.restore();
|
|
1416
|
+
done();
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
global.html2canvas = originalHtml2canvas;
|
|
1419
|
+
window.html2canvas = originalWindowHtml2canvas;
|
|
1420
|
+
exportPDFStub.restore();
|
|
1421
|
+
done(error);
|
|
1422
|
+
}
|
|
1423
|
+
}, 0);
|
|
1424
|
+
});
|
|
1425
|
+
});
|
|
1426
|
+
});
|