orgchart 4.0.1 → 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.
Files changed (77) hide show
  1. package/.github/workflows/publish.yml +32 -0
  2. package/README.md +205 -89
  3. package/README.zh-cn.md +221 -68
  4. package/README.zh-tw.md +221 -68
  5. package/bower.json +6 -9
  6. package/demo/10000-nodes.html +139 -0
  7. package/demo/ajax-datasource.html +46 -45
  8. package/demo/bottom2top.html +12 -9
  9. package/demo/center-on-selected-node.html +21 -17
  10. package/demo/color-coded.html +12 -9
  11. package/demo/css/style.css +28 -0
  12. package/demo/custom-icons.html +12 -9
  13. package/demo/custom-template.html +12 -9
  14. package/demo/data-prop-compact.html +11 -8
  15. package/demo/data-prop-hybrid.html +79 -50
  16. package/demo/default-collapsed.html +12 -9
  17. package/demo/drag-drop-hybrid-chart.html +17 -14
  18. package/demo/drag-drop.html +21 -16
  19. package/demo/edit-chart.html +148 -94
  20. package/demo/edit-hybrid-chart.html +178 -135
  21. package/demo/expand-to-given-depth.html +30 -22
  22. package/demo/export-chart-pdf.html +12 -9
  23. package/demo/export-chart-with-pictures.html +19 -12
  24. package/demo/export-chart.html +12 -9
  25. package/demo/family-tree.html +100 -0
  26. package/demo/familytree-custom-properties.html +108 -0
  27. package/demo/filter-node.html +104 -57
  28. package/demo/get-hierarchy.html +17 -12
  29. package/demo/get-related-nodes.html +27 -17
  30. package/demo/index.html +7 -4
  31. package/demo/integrate-map.html +32 -27
  32. package/demo/left2right.html +12 -9
  33. package/demo/level-offset.html +16 -15
  34. package/demo/link-node.html +12 -9
  35. package/demo/local-datasource.html +11 -8
  36. package/demo/minimap.html +224 -0
  37. package/demo/nodes-of-different-widths.html +11 -8
  38. package/demo/ondemand-loading-data.html +88 -91
  39. package/demo/option-createNode.html +31 -20
  40. package/demo/pan-zoom.html +20 -11
  41. package/demo/reload-data.html +17 -14
  42. package/demo/report-path.html +59 -29
  43. package/demo/responsive-design.html +14 -11
  44. package/demo/right2left.html +12 -9
  45. package/demo/toggle-sibs-resp.html +12 -9
  46. package/demo/ul-datasource.html +11 -8
  47. package/demo/vertical-level.html +12 -9
  48. package/dist/css/{jquery.orgchart.css → orgchart.css} +210 -78
  49. package/dist/css/orgchart.min.css +1 -0
  50. package/dist/js/orgchart.js +4101 -0
  51. package/dist/js/orgchart.min.js +2 -0
  52. package/dist/js/orgchart.min.js.map +1 -0
  53. package/gulpfile.js +37 -26
  54. package/package.json +32 -32
  55. package/src/css/{jquery.orgchart.css → orgchart.css} +210 -78
  56. package/src/js/orgchart.js +4101 -0
  57. package/test/cypress/e2e/demo-interactions.cy.js +91 -0
  58. package/test/cypress/e2e/demo-rendering.cy.js +100 -0
  59. package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +22 -0
  60. package/test/cypress/e2e/edit-chart.cy.js +20 -0
  61. package/test/cypress/e2e/level-offset.cy.js +21 -0
  62. package/test/cypress/e2e/minimap.cy.js +66 -0
  63. package/test/cypress/e2e/pan-zoom.cy.js +165 -0
  64. package/test/cypress/e2e/report-path.cy.js +20 -0
  65. package/test/cypress/e2e/vertical-vevel.cy.js +5 -3
  66. package/test/e2e/local-datasource/test.js +1 -1
  67. package/test/integration/init-tests.js +168 -62
  68. package/test/integration/tests.js +1034 -56
  69. package/test/unit/lifecycle-builders-export.tests.js +1426 -0
  70. package/test/unit/minimap.tests.js +210 -0
  71. package/test/unit/tests.js +2372 -252
  72. package/demo/multiple-layers.html +0 -128
  73. package/dist/css/jquery.orgchart.min.css +0 -1
  74. package/dist/js/jquery.orgchart.js +0 -1686
  75. package/dist/js/jquery.orgchart.min.js +0 -5
  76. package/dist/js/jquery.orgchart.min.js.map +0 -1
  77. package/src/js/jquery.orgchart.js +0 -1686
@@ -1,16 +1,139 @@
1
- var chai = require("chai");
2
- var sinon = require("sinon");
3
- var sinonChai = require("sinon-chai");
4
- var should = chai.should();
1
+ const chai = require("chai");
2
+ const sinon = require("sinon");
3
+ const sinonChai = require("sinon-chai").default;
4
+ const should = chai.should();
5
5
  chai.use(sinonChai);
6
6
  require('jsdom-global')();
7
- var $ = require('jquery');
8
- require('../../src/js/jquery.orgchart');
7
+ process.env.ORGCHART_TEST = '1';
8
+ const OrgChart = require('../../src/js/orgchart');
9
+ const getState = OrgChart.__testing__.getState;
10
+ const setState = OrgChart.__testing__.setState;
11
+
12
+ function query(selector, root) {
13
+ return (root || document).querySelector(selector);
14
+ }
15
+
16
+ function queryAll(selector, root) {
17
+ return Array.from((root || document).querySelectorAll(selector));
18
+ }
19
+
20
+ function createElementFromHtml(html) {
21
+ const template = document.createElement('template');
22
+
23
+ template.innerHTML = html.trim();
24
+ return template.content.firstElementChild;
25
+ }
26
+
27
+ function siblingElements(element, selector) {
28
+ if (!element || !element.parentElement) {
29
+ return [];
30
+ }
31
+ return Array.from(element.parentElement.children).filter(function (childEl) {
32
+ return childEl !== element && (!selector || childEl.matches(selector));
33
+ });
34
+ }
35
+
36
+ function childElements(element, selector) {
37
+ return Array.from(element ? element.children : []).filter(function (childEl) {
38
+ return !selector || childEl.matches(selector);
39
+ });
40
+ }
41
+
42
+ function closestElement(element, selector) {
43
+ return element ? element.closest(selector) : null;
44
+ }
45
+
46
+ function firstSibling(element, selector) {
47
+ return siblingElements(element, selector)[0] || null;
48
+ }
49
+
50
+ function previousSiblings(element, selector) {
51
+ const siblings = [];
52
+ let current = element ? element.previousElementSibling : null;
53
+
54
+ while (current) {
55
+ if (!selector || current.matches(selector)) {
56
+ siblings.push(current);
57
+ }
58
+ current = current.previousElementSibling;
59
+ }
60
+
61
+ return siblings;
62
+ }
63
+
64
+ function nextSiblings(element, selector) {
65
+ const siblings = [];
66
+ let current = element ? element.nextElementSibling : null;
67
+
68
+ while (current) {
69
+ if (!selector || current.matches(selector)) {
70
+ siblings.push(current);
71
+ }
72
+ current = current.nextElementSibling;
73
+ }
74
+
75
+ return siblings;
76
+ }
77
+
78
+ function addClasses(element, classNames) {
79
+ if (!element) {
80
+ return element;
81
+ }
82
+
83
+ classNames.split(/\s+/).filter(Boolean).forEach(function (className) {
84
+ element.classList.add(className);
85
+ });
86
+
87
+ return element;
88
+ }
89
+
90
+ function removeClasses(element, classNames) {
91
+ if (!element) {
92
+ return element;
93
+ }
94
+
95
+ classNames.split(/\s+/).filter(Boolean).forEach(function (className) {
96
+ element.classList.remove(className);
97
+ });
98
+
99
+ return element;
100
+ }
101
+
102
+ function matchesSelector(element, selector) {
103
+ return !!(element && element.matches(selector));
104
+ }
105
+
106
+ function setOneTimeListener(element, type, data, listener) {
107
+ function once(event) {
108
+ event.data = data;
109
+ element.removeEventListener(type, once);
110
+ listener.call(element, event);
111
+ }
112
+
113
+ element.addEventListener(type, once);
114
+ }
115
+
116
+ function triggerEvent(element, type) {
117
+ element.dispatchEvent(new window.Event(type, { bubbles: true }));
118
+ }
119
+
120
+ function refreshNodeReferences(chartEl) {
121
+ $laolao = query('#n1', chartEl);
122
+ $bomiao = query('#n2', chartEl);
123
+ $sumiao = query('#n3', chartEl);
124
+ $hongmiao = query('#n4', chartEl);
125
+ $tiehua = query('#n5', chartEl);
126
+ $heihei = query('#n6', chartEl);
127
+ $pangpang = query('#n7', chartEl);
128
+ $dandan = query('#n8', chartEl);
129
+ $erdan = query('#n9', chartEl);
130
+ $sandan = query('#n10', chartEl);
131
+ }
9
132
 
10
133
  describe('orgchart -- unit tests', function () {
11
- document.body.innerHTML = '<div id="chart-container"></div>';
12
- var $container = $('#chart-container'),
13
- ds = {
134
+ let $container;
135
+
136
+ const ds = {
14
137
  'id': 'n1',
15
138
  'name': 'Lao Lao',
16
139
  'title': 'general manager',
@@ -37,9 +160,40 @@ describe('orgchart -- unit tests', function () {
37
160
  },
38
161
  { 'id': 'n4', 'name': 'Hong Miao', 'title': 'department manager' }
39
162
  ]
40
- },
41
- oc = {},
42
- hierarchy = {
163
+ };
164
+
165
+ const baseDs = JSON.stringify({
166
+ 'id': 'n1',
167
+ 'name': 'Lao Lao',
168
+ 'title': 'general manager',
169
+ 'children': [
170
+ { 'id': 'n2', 'name': 'Bo Miao', 'title': 'department manager' },
171
+ { 'id': 'n3', 'name': 'Su Miao', 'title': 'department manager',
172
+ 'children': [
173
+ { 'id': 'n5', 'name': 'Tie Hua', 'title': 'senior engineer',
174
+ 'children': [
175
+ { 'id': 'n8', 'name': 'Dan Dan', 'title': 'engineer' }
176
+ ]
177
+ },
178
+ { 'id': 'n6', 'name': 'Hei Hei', 'title': 'senior engineer',
179
+ 'children': [
180
+ { 'id': 'n9', 'name': 'Er Dan', 'title': 'engineer' }
181
+ ]
182
+ },
183
+ { 'id': 'n7', 'name': 'Pang Pang', 'title': 'senior engineer',
184
+ 'children': [
185
+ { 'id': 'n10', 'name': 'San Dan', 'title': 'engineer' }
186
+ ]
187
+ }
188
+ ]
189
+ },
190
+ { 'id': 'n4', 'name': 'Hong Miao', 'title': 'department manager' }
191
+ ]
192
+ });
193
+
194
+ let oc = {};
195
+
196
+ const hierarchy = {
43
197
  id: 'n1',
44
198
  children: [
45
199
  { id: 'n2' },
@@ -64,49 +218,80 @@ describe('orgchart -- unit tests', function () {
64
218
  },
65
219
  { id: 'n4' }
66
220
  ]
67
- },
68
- $laolao,
69
- $bomiao,
70
- $sumiao,
71
- $hongmiao,
72
- $chunmiao,
73
- $tiehua,
74
- $heihei,
75
- $pangpang,
76
- $dandan,
77
- $erdan,
78
- $sandan;
221
+ };
222
+
223
+ let $laolao;
224
+ let $bomiao;
225
+ let $sumiao;
226
+ let $hongmiao;
227
+ let $chunmiao;
228
+ let $tiehua;
229
+ let $heihei;
230
+ let $pangpang;
231
+ let $dandan;
232
+ let $erdan;
233
+ let $sandan;
79
234
 
80
235
  beforeEach(function () {
81
- oc = $('#chart-container').orgchart({
236
+ document.body.innerHTML = '<div id="chart-container"></div>';
237
+ $container = document.getElementById('chart-container');
238
+ oc = new OrgChart({
239
+ chartContainer: '#chart-container',
82
240
  'data': ds,
83
241
  'nodeContent': 'title'
84
242
  }),
85
- $laolao = $('#n1'),
86
- $bomiao = $('#n2'),
87
- $sumiao = $('#n3'),
88
- $hongmiao = $('#n4'),
89
- $tiehua = $('#n5'),
90
- $heihei = $('#n6'),
91
- $pangpang = $('#n7'),
92
- $dandan = $('#n8');
93
- $erdan = $('#n9');
94
- $sandan = $('#n10');
95
- });
96
-
243
+ $laolao = document.getElementById('n1'),
244
+ $bomiao = document.getElementById('n2'),
245
+ $sumiao = document.getElementById('n3'),
246
+ $hongmiao = document.getElementById('n4'),
247
+ $tiehua = document.getElementById('n5'),
248
+ $heihei = document.getElementById('n6'),
249
+ $pangpang = document.getElementById('n7'),
250
+ $dandan = document.getElementById('n8');
251
+ $erdan = document.getElementById('n9');
252
+ $sandan = document.getElementById('n10');
253
+ });
254
+
97
255
  afterEach(function () {
98
256
  $laolao = $bomiao = $sumiao = $hongmiao = $tiehua = $heihei = $pangpang = $dandan = $erdan = $sandan = null;
99
- $container.empty();
257
+ $container.innerHTML = '';
100
258
  });
101
259
 
102
260
  it('loopChart()', function () {
103
- oc.loopChart($('.orgchart')).should.deep.equal(hierarchy);
261
+ oc.loopChart(query('.orgchart')).should.deep.equal(hierarchy);
262
+ });
263
+
264
+ it('exposes native chartContainer and chart instance properties', function () {
265
+ oc.chartContainer.should.equal($container);
266
+ oc.chartContainer.firstElementChild.should.equal(oc.chart);
267
+ oc.chart.should.have.property('nodeType', 1);
268
+ });
269
+
270
+ it('buildJsonDS() accepts a native li element', function () {
271
+ const ulContainer = document.createElement('ul');
272
+
273
+ ulContainer.innerHTML = '<li data-id="root" data-title="chief">Root<ul><li data-id="child">Child</li></ul></li>';
274
+
275
+ oc.buildJsonDS(ulContainer.firstElementChild).should.deep.equal({
276
+ id: 'root',
277
+ title: 'chief',
278
+ name: 'Root',
279
+ relationship: '001',
280
+ children: [
281
+ {
282
+ id: 'child',
283
+ name: 'Child',
284
+ relationship: '100'
285
+ }
286
+ ]
287
+ });
104
288
  });
105
289
 
106
290
  it('getHierarchy()', function () {
107
291
  oc.getHierarchy().should.deep.equal(hierarchy);
108
292
 
109
- var oc2 = $('#chart-container').orgchart({
293
+ const oc2 = new OrgChart({
294
+ chartContainer: '#chart-container',
110
295
  'data': { name: 'Lao Lao',
111
296
  'children': [
112
297
  { name: 'Bo Miao' }
@@ -114,249 +299,617 @@ describe('orgchart -- unit tests', function () {
114
299
  }
115
300
  });
116
301
  oc2.getHierarchy().should.include('Error');
117
- oc2.$chart.empty();
302
+ oc2.chart.innerHTML = "";
118
303
  oc2.getHierarchy().should.include('Error');
119
- oc2.$chart = undefined;
304
+ oc2.chart = null;
120
305
  oc2.getHierarchy().should.include('Error');
121
306
  });
122
307
 
123
- it('getNodeState()', function (done) {
124
- var check = function () {
125
- try {
126
- $laolao = $('#n1');
127
- $bomiao = $('#n2');
128
- $sumiao = $('#n3');
129
- $hongmiao = $('#n4');
130
- $tiehua = $('#n5');
131
- $heihei = $('#n6');
132
- $pangpang = $('#n7');
133
- $dandan = $('#n8');
134
- $erdan = $('#n9');
135
- $sandan = $('#n10');
136
-
137
- oc.getNodeState($laolao).should.deep.equal({ 'exist': true, 'visible': true }, "laolao->self");
138
- oc.getNodeState($laolao, 'parent').should.deep.equal({ 'exist': false, 'visible': false }, "laolao->parent");
139
- oc.getNodeState($laolao, 'children').should.deep.equal({ 'exist': true, 'visible': true }, "laolao->children");
140
- oc.getNodeState($laolao, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, "laolao->siblings");
141
-
142
- oc.getNodeState($bomiao).should.deep.equal({ 'exist': true, 'visible': true }, "bomiao->self");
143
- oc.getNodeState($bomiao, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, "bomiao->parent");
144
- oc.getNodeState($bomiao, 'children').should.deep.equal({ 'exist': false, 'visible': false }, "bomiao->children");
145
- oc.getNodeState($bomiao, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, "bomiao->siblings");
146
-
147
- oc.getNodeState($sumiao).should.deep.equal({ 'exist': true, 'visible': true }, "sumiao->self");
148
- oc.getNodeState($sumiao, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, "sumiao->parent");
149
- oc.getNodeState($sumiao, 'children').should.deep.equal({ 'exist': true, 'visible': false }, "sumiao->children");
150
- oc.getNodeState($sumiao, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, "sumiao->siblings");
151
-
152
- oc.getNodeState($tiehua).should.deep.equal({ 'exist': true, 'visible': false }, "tiehua->self");
153
- oc.getNodeState($tiehua, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, "tiehua->parent");
154
- oc.getNodeState($tiehua, 'children').should.deep.equal({ 'exist': true, 'visible': false }, "tiehua->children");
155
- oc.getNodeState($tiehua, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, "tiehua->siblings");
156
-
157
- oc.getNodeState($heihei).should.deep.equal({ 'exist': true, 'visible': false }, "heihei->self");
158
- oc.getNodeState($heihei, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, "heihei->parent");
159
- oc.getNodeState($heihei, 'children').should.deep.equal({ 'exist': true, 'visible': false }, "heihei->children");
160
- oc.getNodeState($heihei, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, "heihei->siblings");
161
-
162
- oc.getNodeState($pangpang).should.deep.equal({ 'exist': true, 'visible': false }, "pangpang->self");
163
- oc.getNodeState($pangpang, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, "pangpang->parent");
164
- oc.getNodeState($pangpang, 'children').should.deep.equal({ 'exist': true, 'visible': false }, "pangpang->children");
165
- oc.getNodeState($pangpang, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, "pangpang->siblings");
166
-
167
- oc.getNodeState($dandan).should.deep.equal({ 'exist': true, 'visible': false }, "dandan->self");
168
- oc.getNodeState($dandan, 'parent').should.deep.equal({ 'exist': true, 'visible': false }, "dandan->parent");
169
- oc.getNodeState($dandan, 'children').should.deep.equal({ 'exist': false, 'visible': false }, "dandan->children");
170
- oc.getNodeState($dandan, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, "dandan->siblings");
308
+ it('triggerShowEvent() accepts a native node', function () {
309
+ const eventSpy = sinon.spy();
171
310
 
172
- done();
173
- } catch(err) {
174
- done(err);
175
- }
311
+ $sumiao.addEventListener('show-children.orgchart', eventSpy);
312
+
313
+ oc.triggerShowEvent($sumiao, 'children');
314
+
315
+ eventSpy.should.have.been.calledOnce;
316
+ eventSpy.firstCall.args[0].type.should.equal('show-children.orgchart');
317
+ });
318
+
319
+ it('triggerHideEvent() accepts a native node', function () {
320
+ const eventSpy = sinon.spy();
321
+
322
+ $sumiao.addEventListener('hide-children.orgchart', eventSpy);
323
+
324
+ oc.triggerHideEvent($sumiao, 'children');
325
+
326
+ eventSpy.should.have.been.calledOnce;
327
+ eventSpy.firstCall.args[0].type.should.equal('hide-children.orgchart');
328
+ });
329
+
330
+ it('getNodeState()', function () {
331
+ const stateDs = JSON.parse(baseDs);
332
+ let chartEl;
333
+ let laolaoEl;
334
+ let bomiaoEl;
335
+ let sumiaoEl;
336
+ let tiehuaEl;
337
+ let heiheiEl;
338
+ let pangpangEl;
339
+ let dandanEl;
340
+
341
+ $container.innerHTML = '';
342
+ oc = new OrgChart({
343
+ chartContainer: '#chart-container',
344
+ data: stateDs,
345
+ nodeContent: 'title',
346
+ visibleLevel: 2,
347
+ verticalLevel: 3
348
+ });
349
+ chartEl = oc.chart;
350
+ laolaoEl = query('#n1', chartEl);
351
+ bomiaoEl = query('#n2', chartEl);
352
+ sumiaoEl = query('#n3', chartEl);
353
+ tiehuaEl = query('#n5', chartEl);
354
+ heiheiEl = query('#n6', chartEl);
355
+ pangpangEl = query('#n7', chartEl);
356
+ dandanEl = query('#n8', chartEl);
357
+
358
+ oc.getNodeState(laolaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->self');
359
+ oc.getNodeState(laolaoEl, 'parent').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->parent');
360
+ oc.getNodeState(laolaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->children');
361
+ oc.getNodeState(laolaoEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->siblings');
362
+
363
+ oc.getNodeState(bomiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->self');
364
+ oc.getNodeState(bomiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->parent');
365
+ oc.getNodeState(bomiaoEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'bomiao->children');
366
+ oc.getNodeState(bomiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->siblings');
367
+
368
+ oc.getNodeState(sumiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->self');
369
+ oc.getNodeState(sumiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->parent');
370
+ oc.getNodeState(sumiaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'sumiao->children');
371
+ oc.getNodeState(sumiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->siblings');
372
+
373
+ oc.getNodeState(tiehuaEl).should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->self');
374
+ oc.getNodeState(tiehuaEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'tiehua->parent');
375
+ oc.getNodeState(tiehuaEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->children');
376
+ oc.getNodeState(tiehuaEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->siblings');
377
+
378
+ oc.getNodeState(heiheiEl).should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->self');
379
+ oc.getNodeState(heiheiEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'heihei->parent');
380
+ oc.getNodeState(heiheiEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->children');
381
+ oc.getNodeState(heiheiEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->siblings');
382
+
383
+ oc.getNodeState(pangpangEl).should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->self');
384
+ oc.getNodeState(pangpangEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'pangpang->parent');
385
+ oc.getNodeState(pangpangEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->children');
386
+ oc.getNodeState(pangpangEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->siblings');
387
+
388
+ oc.getNodeState(dandanEl).should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->self');
389
+ oc.getNodeState(dandanEl, 'parent').should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->parent');
390
+ oc.getNodeState(dandanEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->children');
391
+ oc.getNodeState(dandanEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->siblings');
392
+ });
393
+
394
+ it('triggerInitEvent() calls initCompleted with the native chart element', function () {
395
+ const originalMutationObserver = global.MutationObserver;
396
+ const originalWindowMutationObserver = window.MutationObserver;
397
+ const observeSpy = sinon.spy();
398
+ const disconnectSpy = sinon.spy();
399
+ const initCompletedSpy = sinon.spy();
400
+
401
+ const fakeMutationObserver = function (callback) {
402
+ this.observe = function (target, options) {
403
+ observeSpy(target, options);
404
+ callback([
405
+ {
406
+ addedNodes: [oc.chart]
407
+ }
408
+ ]);
409
+ };
410
+ this.disconnect = disconnectSpy;
176
411
  };
177
- if (typeof MutationObserver !== 'undefined') {
178
- oc.init({ 'visibleLevel': 2, 'verticalLevel': 3 }).$chart.one('init.orgchart', check);
179
- } else {
180
- oc.init({ 'visibleLevel': 2, 'verticalLevel': 3 });
181
- setTimeout(check, 0);
182
- }
412
+
413
+ global.MutationObserver = fakeMutationObserver;
414
+ window.MutationObserver = fakeMutationObserver;
415
+
416
+ oc.options.initCompleted = initCompletedSpy;
417
+ oc.triggerInitEvent();
418
+
419
+ observeSpy.should.have.been.calledOnce;
420
+ observeSpy.firstCall.args[0].should.equal($container);
421
+ observeSpy.firstCall.args[1].should.deep.equal({ childList: true });
422
+ disconnectSpy.should.have.been.calledOnce;
423
+ initCompletedSpy.should.have.been.calledOnce;
424
+ initCompletedSpy.firstCall.args[0].should.equal(oc.chart);
425
+
426
+ global.MutationObserver = originalMutationObserver;
427
+ window.MutationObserver = originalWindowMutationObserver;
428
+ });
429
+
430
+ it('triggerInitEvent() triggers init.orgchart on the chart collection', function () {
431
+ const originalMutationObserver = global.MutationObserver;
432
+ const originalWindowMutationObserver = window.MutationObserver;
433
+ const eventSpy = sinon.spy();
434
+
435
+ const fakeMutationObserver = function (callback) {
436
+ this.observe = function () {
437
+ callback([
438
+ {
439
+ addedNodes: [oc.chart]
440
+ }
441
+ ]);
442
+ };
443
+ this.disconnect = function () {};
444
+ };
445
+
446
+ global.MutationObserver = fakeMutationObserver;
447
+ window.MutationObserver = fakeMutationObserver;
448
+ oc.chart.addEventListener('init.orgchart', eventSpy);
449
+
450
+ oc.triggerInitEvent();
451
+
452
+ eventSpy.should.have.been.calledOnce;
453
+ eventSpy.firstCall.args[0].type.should.equal('init.orgchart');
454
+
455
+ global.MutationObserver = originalMutationObserver;
456
+ window.MutationObserver = originalWindowMutationObserver;
457
+ });
458
+
459
+ it('handleCompactNodes() tags compact nodes by compact-ancestor parity', function () {
460
+ const compactRootEl = document.createElement('div');
461
+ const compactChildEl = document.createElement('div');
462
+ const compactGrandchildEl = document.createElement('div');
463
+
464
+ compactRootEl.className = 'node compact';
465
+ compactChildEl.className = 'node compact';
466
+ compactGrandchildEl.className = 'node compact';
467
+ compactRootEl.appendChild(compactChildEl);
468
+ compactChildEl.appendChild(compactGrandchildEl);
469
+ oc.chart.appendChild(compactRootEl);
470
+
471
+ oc.handleCompactNodes();
472
+
473
+ compactRootEl.classList.contains('even').should.equal(true);
474
+ compactChildEl.classList.contains('odd').should.equal(true);
475
+ compactGrandchildEl.classList.contains('even').should.equal(true);
183
476
  });
184
477
 
185
478
  it('getRelatedNodes()', function () {
186
- oc.getRelatedNodes().should.deep.equal($());
187
- oc.getRelatedNodes({}, 'children').should.deep.equal($());
188
- oc.getRelatedNodes($('.hierarchy:first'), 'children').should.deep.equal($());
189
- oc.getRelatedNodes($('.node:first'), 'child').should.deep.equal($());
479
+ oc.getRelatedNodes().should.deep.equal([]);
480
+ oc.getRelatedNodes({}, 'children').should.deep.equal([]);
481
+ oc.getRelatedNodes(query('.hierarchy', oc.chart), 'children').should.deep.equal([]);
482
+ oc.getRelatedNodes(query('.node', oc.chart), 'child').should.deep.equal([]);
190
483
 
191
- oc.getRelatedNodes($laolao, 'parent').should.deep.equal($());
192
- oc.getRelatedNodes($laolao, 'children').toArray().should.members([$bomiao[0], $sumiao[0], $hongmiao[0]]);
193
- oc.getRelatedNodes($laolao, 'siblings').should.deep.equal($());
484
+ should.equal(oc.getRelatedNodes($laolao, 'parent'), null);
485
+ oc.getRelatedNodes($laolao, 'children').should.have.members([$bomiao, $sumiao, $hongmiao]);
486
+ oc.getRelatedNodes($laolao, 'siblings').should.deep.equal([]);
194
487
 
195
- oc.getRelatedNodes($bomiao, 'parent').should.deep.equal($laolao);
488
+ oc.getRelatedNodes($bomiao, 'parent').should.equal($laolao);
196
489
  oc.getRelatedNodes($bomiao, 'children').should.have.lengthOf(0);
197
- oc.getRelatedNodes($bomiao, 'siblings').toArray().should.members([$sumiao[0], $hongmiao[0]]);
490
+ oc.getRelatedNodes($bomiao, 'siblings').should.have.members([$sumiao, $hongmiao]);
198
491
  });
199
492
 
200
493
  it('getParent()', function () {
201
- oc.getParent().should.deep.equal($());
202
- oc.getParent({}).should.deep.equal($());
203
- oc.getParent($('.hierarchy:first')).should.deep.equal($());
494
+ should.equal(oc.getParent(), null);
495
+ should.equal(oc.getParent({}), null);
496
+ should.equal(oc.getParent(query('.hierarchy', oc.chart)), null);
204
497
 
205
- oc.getParent($laolao).should.deep.equal($());
206
- oc.getParent($bomiao).should.deep.equal($laolao);
207
- oc.getParent($sandan).should.deep.equal($pangpang);
498
+ should.equal(oc.getParent($laolao), null);
499
+ oc.getParent($bomiao).should.equal($laolao);
500
+ oc.getParent($sandan).should.equal($pangpang);
208
501
  });
209
502
 
210
503
  it('getSiblings()', function () {
211
- oc.getSiblings().should.deep.equal($());
212
- oc.getSiblings({}).should.deep.equal($());
213
- oc.getSiblings($('.hierarchy:first')).should.deep.equal($());
504
+ oc.getSiblings().should.deep.equal([]);
505
+ oc.getSiblings({}).should.deep.equal([]);
506
+ oc.getSiblings(query('.hierarchy', oc.chart)).should.deep.equal([]);
214
507
 
215
- oc.getSiblings($laolao).should.deep.equal($());
216
- oc.getSiblings($bomiao).toArray().should.members([$sumiao[0], $hongmiao[0]]);
217
- oc.getSiblings($sandan).should.deep.equal($());
508
+ oc.getSiblings($laolao).should.deep.equal([]);
509
+ oc.getSiblings($bomiao).should.have.members([$sumiao, $hongmiao]);
510
+ oc.getSiblings($sandan).should.deep.equal([]);
218
511
  });
219
512
 
220
513
  it('getChildren()', function () {
221
- oc.getChildren().should.deep.equal($());
222
- oc.getChildren({}).should.deep.equal($());
223
- oc.getChildren($('.hierarchy:first')).should.deep.equal($());
514
+ oc.getChildren().should.deep.equal([]);
515
+ oc.getChildren({}).should.deep.equal([]);
516
+ oc.getChildren(query('.hierarchy', oc.chart)).should.deep.equal([]);
224
517
 
225
- oc.getChildren($laolao).toArray().should.members([$bomiao[0], $sumiao[0], $hongmiao[0]]);
226
- oc.getChildren($bomiao).should.deep.equal($());
227
- oc.getChildren($sumiao).toArray().should.members([$tiehua[0], $heihei[0], $pangpang[0]]);
228
- oc.getChildren($sandan).should.deep.equal($());
518
+ oc.getChildren($laolao).should.have.members([$bomiao, $sumiao, $hongmiao]);
519
+ oc.getChildren($bomiao).should.deep.equal([]);
520
+ oc.getChildren($sumiao).should.have.members([$tiehua, $heihei, $pangpang]);
521
+ oc.getChildren($sandan).should.deep.equal([]);
229
522
  });
230
523
 
231
524
  it('hideParent()', function () {
232
- var spy = sinon.spy(oc, 'hideParent');
233
- var spy2 = sinon.spy(oc, 'hideSiblings');
525
+ const spy = sinon.spy(oc, 'hideParent');
526
+ const spy2 = sinon.spy(oc, 'hideSiblings');
527
+
234
528
  oc.hideParent($heihei);
529
+ setOneTimeListener($sumiao, 'transitionend', null, function (event) {
530
+ event.parent = parentEl;
531
+ spy.call(oc, event);
532
+ });
235
533
  spy.should.have.been.callCount(2);
236
- spy.getCall(0).should.have.been.calledWithMatch($heihei);
237
- spy.getCall(1).should.have.been.calledWithMatch($sumiao);
534
+ spy.getCall(0).args[0].should.equal($heihei);
535
+ spy.getCall(1).args[0].should.equal($sumiao);
238
536
  spy2.should.have.been.callCount(2);
239
- spy2.getCall(0).should.have.been.calledWithMatch($heihei);
240
- spy2.getCall(1).should.have.been.calledWithMatch($sumiao);
537
+ spy2.getCall(0).args[0].should.equal($heihei);
538
+ spy2.getCall(1).args[0].should.equal($sumiao);
539
+ });
540
+
541
+ it('hideParent() hides the transitioned parent node', function () {
542
+ oc.hideParent($sumiao);
543
+
544
+ triggerEvent($laolao, 'transitionend');
545
+
546
+ $laolao.classList.contains('sliding').should.equal(false);
547
+ $laolao.classList.contains('hidden').should.equal(true);
548
+ });
549
+
550
+ it('hideParent() clears inAjax when the parent node contains a spinner', function () {
551
+ const spinnerEl = document.createElement('i');
552
+
553
+ spinnerEl.className = 'spinner';
554
+ $sumiao.appendChild(spinnerEl);
555
+ setState(oc.chart, 'inAjax', true);
556
+
557
+ oc.hideParent($heihei);
558
+
559
+ getState(oc.chart, 'inAjax').should.be.false;
560
+ spinnerEl.remove();
241
561
  });
242
562
 
243
563
  it('hideParentEnd()', function () {
244
- var spy = sinon.spy(oc, 'hideParentEnd');
245
- var $parent = $heihei.closest('.nodes').siblings('.node');
246
- $sumiao.addClass('sliding slide-down').one('transitionend', { 'parent': $parent }, spy.bind(oc));
247
- $sumiao.trigger('transitionend');
248
- spy.should.have.been.called;
249
- $sumiao.is('sliding').should.be.false;
250
- $parent.is('.hidden').should.be.true;
564
+ const parentEl = firstSibling(closestElement($heihei, '.nodes'), '.node');
565
+
566
+ addClasses($sumiao, 'sliding slide-down');
567
+ oc.hideParentEnd({ animatedNode: $sumiao, parent: parentEl });
568
+ matchesSelector($sumiao, 'sliding').should.be.false;
569
+ matchesSelector(parentEl, '.hidden').should.be.true;
570
+ });
571
+
572
+ it('hideParentEnd() accepts native animated and parent payloads', function () {
573
+ addClasses($sumiao, 'sliding');
574
+ removeClasses($sumiao, 'hidden');
575
+
576
+ oc.hideParentEnd({
577
+ animatedNode: $sumiao,
578
+ parent: $sumiao
579
+ });
580
+
581
+ $sumiao.classList.contains('sliding').should.equal(false);
582
+ $sumiao.classList.contains('hidden').should.equal(true);
583
+ });
584
+
585
+ it('hideParentEnd() accepts wrapped native animated and parent payloads', function () {
586
+ addClasses($sumiao, 'sliding');
587
+
588
+ oc.hideParentEnd({
589
+ originalEvent: {
590
+ target: $sumiao,
591
+ parent: $heihei
592
+ }
593
+ });
594
+
595
+ $sumiao.classList.contains('sliding').should.equal(false);
596
+ $heihei.classList.contains('hidden').should.equal(true);
251
597
  });
252
598
 
253
599
  it('showParent()', function () {
254
- var spy = sinon.spy(oc, 'repaint');
255
- $heihei.closest('.hierarchy').addClass('isAncestorsCollapsed');
256
- $sumiao.addClass('slide-down hidden').closest('.hierarchy').addClass('isAncestorsCollapsed');
257
- $laolao.addClass('slide-down hidden');
600
+ const spy = sinon.spy(oc, 'repaint');
601
+
602
+ addClasses(closestElement($heihei, '.hierarchy'), 'isAncestorsCollapsed');
603
+ addClasses($sumiao, 'slide-down hidden');
604
+ addClasses(closestElement($sumiao, '.hierarchy'), 'isAncestorsCollapsed');
605
+ addClasses($laolao, 'slide-down hidden');
258
606
  oc.showParent($heihei);
259
607
  spy.should.have.been.called;
260
- $heihei.closest('.hierarchy').is('.isAncestorsCollapsed').should.be.false;
261
- $sumiao.is('.slide-down, .hidden').should.be.false;
262
- $sumiao.closest('.hierarchy').is('.isAncestorsCollapsed').should.be.true;
263
- $laolao.is('.slide-down.hidden').should.be.true;
264
- $sumiao.is('.sliding').should.be.true;
265
- $sumiao.is('.slide-down').should.be.false;
608
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isAncestorsCollapsed').should.be.false;
609
+ matchesSelector($sumiao, '.slide-down, .hidden').should.be.false;
610
+ matchesSelector(closestElement($sumiao, '.hierarchy'), '.isAncestorsCollapsed').should.be.true;
611
+ matchesSelector($laolao, '.slide-down.hidden').should.be.true;
612
+ matchesSelector($sumiao, '.sliding').should.be.true;
613
+ matchesSelector($sumiao, '.slide-down').should.be.false;
266
614
  });
267
615
 
268
616
  it('showParentEnd()', function () {
269
- var spy = sinon.spy(oc, 'showParentEnd');
270
- var spy2 = sinon.spy(oc, 'isInAction');
271
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
272
- $sumiao.addClass('sliding').removeClass('slide-down').one('transitionend', { 'node': $heihei }, spy.bind(oc));
273
- $sumiao.trigger('transitionend');
617
+ const spy = sinon.spy(oc, 'showParentEnd');
618
+ const spy2 = sinon.spy(oc, 'isInAction');
619
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
620
+
621
+ addClasses($sumiao, 'sliding');
622
+ removeClasses($sumiao, 'slide-down');
623
+ setOneTimeListener($sumiao, 'transitionend', null, function (event) {
624
+ event.node = $heihei;
625
+ spy.call(oc, event);
626
+ });
627
+ triggerEvent($sumiao, 'transitionend');
274
628
  spy.should.have.been.called;
275
- $sumiao.is('.sliding').should.be.false;
629
+ matchesSelector($sumiao, '.sliding').should.be.false;
276
630
  spy2.should.have.been.calledWith($heihei);
277
631
  spy3.should.not.have.been.called;
278
632
  });
279
633
 
634
+ it('showParentEnd() accepts native animated and node payloads', function () {
635
+ addClasses($sumiao, 'sliding');
636
+
637
+ oc.showParentEnd({
638
+ animatedNode: $sumiao,
639
+ node: $heihei
640
+ });
641
+
642
+ $sumiao.classList.contains('sliding').should.equal(false);
643
+ });
644
+
645
+ it('showParentEnd() accepts wrapped native animated and node payloads', function () {
646
+ addClasses($sumiao, 'sliding');
647
+
648
+ oc.showParentEnd({
649
+ originalEvent: {
650
+ target: $sumiao,
651
+ node: $heihei
652
+ }
653
+ });
654
+
655
+ $sumiao.classList.contains('sliding').should.equal(false);
656
+ });
657
+
280
658
  it('hideChildren()', function () {
281
- var spy = sinon.spy(oc, 'repaint');
659
+ const spy = sinon.spy(oc, 'repaint');
660
+
282
661
  oc.hideChildren($sumiao);
662
+
283
663
  spy.should.have.been.called;
284
- $sumiao.closest('.hierarchy').is('.isChildrenCollapsed').should.be.true;
285
- $sumiao.siblings('.nodes').children('.isCollapsedDescendant').should.lengthOf(3);
286
- $tiehua.is('.sliding.slide-up').should.be.true;
287
- $heihei.is('.sliding.slide-up').should.be.true;
288
- $pangpang.is('.sliding.slide-up').should.be.true;
289
- $erdan.is('.sliding.slide-up').should.be.true;
664
+ matchesSelector(closestElement($sumiao, '.hierarchy'), '.isChildrenCollapsed').should.be.true;
665
+ childElements(firstSibling($sumiao, '.nodes'), '.isCollapsedDescendant').should.lengthOf(3);
666
+ matchesSelector($tiehua, '.sliding.slide-up').should.be.true;
667
+ matchesSelector($heihei, '.sliding.slide-up').should.be.true;
668
+ matchesSelector($pangpang, '.sliding.slide-up').should.be.true;
669
+ matchesSelector($erdan, '.sliding.slide-up').should.be.true;
290
670
  });
291
671
 
292
672
  it('hideChildrenEnd()', function () {
293
- var spy = sinon.spy(oc, 'hideChildrenEnd');
294
- var spy2 = sinon.spy(oc, 'isInAction');
295
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
296
- $tiehua.addClass('sliding slide-up').one('transitionend', { 'animatedNodes': $tiehua, 'lowerLevel': $sumiao.closest('tr').siblings(), 'isVerticalDesc': false, 'node': $sumiao }, spy.bind(oc));
297
- $tiehua.trigger('transitionend');
298
- spy.should.have.been.called;
299
- $tiehua.is('.sliding').should.be.false;
300
- $tiehua.closest('.nodes').is('.hidden').should.be.true;
673
+ const spy2 = sinon.spy(oc, 'isInAction');
674
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
675
+
676
+ addClasses($tiehua, 'sliding slide-up');
677
+ oc.hideChildrenEnd({ animatedNodes: $tiehua, node: $sumiao });
678
+ matchesSelector($tiehua, '.sliding').should.be.false;
679
+ matchesSelector(closestElement($tiehua, '.nodes'), '.hidden').should.be.true;
301
680
  spy2.should.have.been.calledWith($sumiao);
302
681
  spy3.should.not.have.been.called;
303
682
  });
304
683
 
684
+ it('hideChildrenEnd() accepts direct animated and node payloads', function () {
685
+ const bottomEdgeEl = query('.bottomEdge', $sumiao);
686
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
687
+
688
+ removeClasses($tiehua, 'hidden');
689
+ addClasses($tiehua, 'sliding');
690
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
691
+ bottomEdgeEl.classList.add(oc.options.icons.collapseToDown);
692
+
693
+ oc.hideChildrenEnd({ animatedNodes: [$tiehua], node: $sumiao });
694
+
695
+ $tiehua.classList.contains('sliding').should.equal(false);
696
+ closestElement($tiehua, '.nodes').classList.contains('hidden').should.equal(true);
697
+ verticalSpy.should.have.been.calledWith(bottomEdgeEl);
698
+
699
+ verticalSpy.restore();
700
+ });
701
+
702
+ it('hideChildrenEnd() accepts wrapped native animated and node payloads', function () {
703
+ const bottomEdgeEl = query('.bottomEdge', $sumiao);
704
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
705
+
706
+ removeClasses($tiehua, 'hidden');
707
+ addClasses($tiehua, 'sliding');
708
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
709
+ bottomEdgeEl.classList.add(oc.options.icons.collapseToDown);
710
+
711
+ oc.hideChildrenEnd({
712
+ originalEvent: {
713
+ animatedNodes: [$tiehua],
714
+ node: $sumiao
715
+ }
716
+ });
717
+
718
+ $tiehua.classList.contains('sliding').should.equal(false);
719
+ closestElement($tiehua, '.nodes').classList.contains('hidden').should.equal(true);
720
+ verticalSpy.should.have.been.calledWith(bottomEdgeEl);
721
+
722
+ verticalSpy.restore();
723
+ });
724
+
305
725
  it('showChildren()', function () {
306
- var spy = sinon.spy(oc, 'repaint');
307
- $sumiao.siblings('.nodes').find('.node').addClass('slide-up');
308
- $sumiao.siblings('.nodes').find('.nodes').addBack().addClass('hidden');
726
+ const spy = sinon.spy(oc, 'repaint');
727
+ const descendantsWrapperEl = firstSibling($sumiao, '.nodes');
728
+
729
+ queryAll('.node', descendantsWrapperEl).forEach(function (nodeEl) {
730
+ addClasses(nodeEl, 'slide-up');
731
+ });
732
+ queryAll('.nodes', descendantsWrapperEl).forEach(function (nodeEl) {
733
+ addClasses(nodeEl, 'hidden');
734
+ });
735
+ addClasses(descendantsWrapperEl, 'hidden');
309
736
  oc.showChildren($sumiao);
310
- spy.should.have.been.calledWith($tiehua[0]);
311
- $tiehua.is('.sliding:not(.slide-up)').should.be.true;
312
- $heihei.is('.sliding:not(.slide-up)').should.be.true;
313
- $pangpang.is('.sliding:not(.slide-up)').should.be.true;
314
- $erdan.is('.slide-up:not(.sliding)').should.be.true;
737
+ spy.should.have.been.calledWith($tiehua);
738
+ matchesSelector($tiehua, '.sliding:not(.slide-up)').should.be.true;
739
+ matchesSelector($heihei, '.sliding:not(.slide-up)').should.be.true;
740
+ matchesSelector($pangpang, '.sliding:not(.slide-up)').should.be.true;
741
+ matchesSelector($erdan, '.slide-up:not(.sliding)').should.be.true;
315
742
  });
316
743
 
317
744
  it('showChildrenEnd()', function () {
318
- var spy = sinon.spy(oc, 'showChildrenEnd');
319
- var spy2 = sinon.spy(oc, 'isInAction');
320
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
321
- $tiehua.addClass('sliding').one('transitionend', { 'node': $sumiao, 'animatedNodes': $tiehua }, spy.bind(oc));
322
- $tiehua.trigger('transitionend');
323
- spy.should.have.been.called;
324
- $tiehua.is('.sliding').should.be.false;
745
+ const spy2 = sinon.spy(oc, 'isInAction');
746
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
747
+
748
+ addClasses($tiehua, 'sliding');
749
+ oc.showChildrenEnd({ node: $sumiao, animatedNodes: $tiehua });
750
+ matchesSelector($tiehua, '.sliding').should.be.false;
325
751
  spy2.should.have.been.calledWith($sumiao);
326
752
  spy3.should.not.have.been.called;
327
753
  });
328
754
 
755
+ it('showChildrenEnd() accepts direct animated and node payloads', function () {
756
+ addClasses($tiehua, 'sliding');
757
+
758
+ oc.showChildrenEnd({
759
+ node: $sumiao,
760
+ animatedNodes: [$tiehua]
761
+ });
762
+
763
+ $tiehua.classList.contains('sliding').should.equal(false);
764
+ });
765
+
766
+ it('showChildrenEnd() accepts wrapped native animated and node payloads', function () {
767
+ addClasses($tiehua, 'sliding');
768
+
769
+ oc.showChildrenEnd({
770
+ originalEvent: {
771
+ node: $sumiao,
772
+ animatedNodes: [$tiehua]
773
+ }
774
+ });
775
+
776
+ $tiehua.classList.contains('sliding').should.equal(false);
777
+ });
778
+
779
+ it('showSiblingsEnd() accepts direct visible and node payloads', function () {
780
+ const topEdgeEl = query('.topEdge', $heihei);
781
+ const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
782
+
783
+ addClasses($tiehua, 'sliding');
784
+ addClasses($pangpang, 'sliding');
785
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
786
+
787
+ oc.showSiblingsEnd({
788
+ node: $heihei,
789
+ visibleNodes: [$tiehua, $pangpang]
790
+ });
791
+
792
+ $tiehua.classList.contains('sliding').should.equal(false);
793
+ $pangpang.classList.contains('sliding').should.equal(false);
794
+ switchSpy.should.have.been.calledWith($heihei);
795
+ topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
796
+ topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
797
+
798
+ switchSpy.restore();
799
+ });
800
+
801
+ it('showSiblingsEnd() accepts wrapped native visible and node payloads', function () {
802
+ const topEdgeEl = query('.topEdge', $heihei);
803
+ const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
804
+
805
+ addClasses($tiehua, 'sliding');
806
+ addClasses($pangpang, 'sliding');
807
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
808
+
809
+ oc.showSiblingsEnd({
810
+ originalEvent: {
811
+ node: $heihei,
812
+ visibleNodes: [$tiehua, $pangpang]
813
+ }
814
+ });
815
+
816
+ $tiehua.classList.contains('sliding').should.equal(false);
817
+ $pangpang.classList.contains('sliding').should.equal(false);
818
+ switchSpy.should.have.been.calledWith($heihei);
819
+ topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
820
+ topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
821
+
822
+ switchSpy.restore();
823
+ });
824
+
825
+ it('showRelatedParentEnd() removes sliding from a native animated node payload', function () {
826
+ addClasses($sumiao, 'sliding');
827
+
828
+ oc.showRelatedParentEnd({ animatedNode: $sumiao });
829
+
830
+ $sumiao.classList.contains('sliding').should.equal(false);
831
+ });
832
+
833
+ it('showRelatedParentEnd() accepts a wrapped native animated node payload', function () {
834
+ addClasses($sumiao, 'sliding');
835
+
836
+ oc.showRelatedParentEnd({
837
+ originalEvent: {
838
+ target: $sumiao
839
+ }
840
+ });
841
+
842
+ $sumiao.classList.contains('sliding').should.equal(false);
843
+ });
844
+
845
+ it('showSiblings() accepts a native node and reveals the requested side with animation', function () {
846
+ const repaintSpy = sinon.spy(oc, 'repaint');
847
+
848
+ addClasses(closestElement($tiehua, '.hierarchy'), 'hidden isCollapsedSibling left-sibs');
849
+ addClasses($tiehua, 'slide-right');
850
+ addClasses(closestElement($heihei, '.hierarchy'), 'isSiblingsCollapsed left-sibs isAncestorsCollapsed');
851
+ addClasses($sumiao, 'hidden slide-down');
852
+
853
+ oc.showSiblings($heihei, 'left');
854
+
855
+ repaintSpy.should.have.been.calledWith($sumiao);
856
+ closestElement($tiehua, '.hierarchy').classList.contains('hidden').should.equal(false);
857
+ closestElement($tiehua, '.hierarchy').classList.contains('isCollapsedSibling').should.equal(false);
858
+ closestElement($heihei, '.hierarchy').classList.contains('left-sibs').should.equal(false);
859
+ closestElement($heihei, '.hierarchy').classList.contains('isSiblingsCollapsed').should.equal(false);
860
+ $tiehua.classList.contains('sliding').should.equal(true);
861
+ $tiehua.classList.contains('slide-right').should.equal(false);
862
+ $sumiao.classList.contains('hidden').should.equal(false);
863
+ $sumiao.classList.contains('sliding').should.equal(true);
864
+ $sumiao.classList.contains('slide-down').should.equal(false);
865
+
866
+ repaintSpy.restore();
867
+ });
868
+
329
869
  describe('hideSiblings()', function () {
330
870
  context('when passing only one parameter -- node', function () {
331
871
  it('should hide all the sibling nodes and their descendants', function () {
332
872
  oc.hideSiblings($heihei);
333
- $tiehua.is('.sliding.slide-right').should.be.true;
334
- $dandan.is('.sliding.slide-right').should.be.true;
335
- $pangpang.is('.sliding.slide-left').should.be.true;
336
- $sandan.is('.sliding.slide-left').should.be.true;
337
- $heihei.closest('.hierarchy').is('.isSiblingsCollapsed').should.be.true;
338
- $heihei.closest('.hierarchy').siblings('.isChildrenCollapsed.isCollapsedSibling').should.lengthOf(2);
873
+ matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
874
+ matchesSelector($dandan, '.sliding.slide-right').should.be.true;
875
+ matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
876
+ matchesSelector($sandan, '.sliding.slide-left').should.be.true;
877
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed').should.be.true;
878
+ siblingElements(closestElement($heihei, '.hierarchy'), '.isChildrenCollapsed.isCollapsedSibling').should.lengthOf(2);
879
+ });
880
+
881
+ it('clears inAjax when a sibling hierarchy contains a spinner', function () {
882
+ const spinnerEl = document.createElement('i');
883
+
884
+ spinnerEl.className = 'spinner';
885
+ closestElement($tiehua, '.hierarchy').appendChild(spinnerEl);
886
+ setState(oc.chart, 'inAjax', true);
887
+
888
+ oc.hideSiblings($heihei);
889
+
890
+ getState(oc.chart, 'inAjax').should.be.false;
891
+ spinnerEl.remove();
339
892
  });
340
893
  });
341
894
 
342
895
  context('when passing two parameters -- node and direction', function () {
343
896
  it('hide the left side sibling nodes and their descendants', function () {
344
897
  oc.hideSiblings($heihei, 'left');
345
- $heihei.closest('.hierarchy').is('.isSiblingsCollapsed.left-sibs').should.be.true;
346
- $heihei.closest('.hierarchy').prev().is('.isChildrenCollapsed.isCollapsedSibling').should.be.true;
347
- $heihei.closest('.hierarchy').next().is('.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
348
- $tiehua.is('.sliding.slide-right').should.be.true;
349
- $dandan.is('.sliding.slide-right').should.be.true;
350
- $pangpang.is('.sliding').should.be.false;
898
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.left-sibs').should.be.true;
899
+ matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
900
+ matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
901
+ matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
902
+ matchesSelector($dandan, '.sliding.slide-right').should.be.true;
903
+ matchesSelector($pangpang, '.sliding').should.be.false;
351
904
  });
352
905
  it('hide the right side sibling nodes and their descendants', function () {
353
906
  oc.hideSiblings($heihei, 'right');
354
- $heihei.closest('.hierarchy').is('.isSiblingsCollapsed.right-sibs').should.be.true;
355
- $heihei.closest('.hierarchy').next().is('.isChildrenCollapsed.isCollapsedSibling').should.be.true;
356
- $heihei.closest('.hierarchy').prev().is('.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
357
- $pangpang.is('.sliding.slide-left').should.be.true;
358
- $sandan.is('.sliding.slide-left').should.be.true;
359
- $tiehua.is('.sliding').should.be.false;
907
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.right-sibs').should.be.true;
908
+ matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
909
+ matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
910
+ matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
911
+ matchesSelector($sandan, '.sliding.slide-left').should.be.true;
912
+ matchesSelector($tiehua, '.sliding').should.be.false;
360
913
  });
361
914
  });
362
915
  });
@@ -364,25 +917,29 @@ describe('orgchart -- unit tests', function () {
364
917
  describe('hideSiblingsEnd()', function () {
365
918
  context('when invoking transitionend event without specifying direction', function () {
366
919
  it('clean up final classList for hidden siblings', function () {
367
- var spy = sinon.spy(oc, 'hideSiblingsEnd');
368
- var spy2 = sinon.spy(oc, 'isInAction');
369
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
370
- var $nodeContainer = $heihei.closest('.hierarchy');
920
+ const spy = sinon.spy(oc, 'hideSiblingsEnd');
921
+ const spy2 = sinon.spy(oc, 'isInAction');
922
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
923
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
924
+
371
925
  oc.hideSiblings($heihei);
372
- $tiehua.one('transitionend', {
926
+
927
+ setOneTimeListener($tiehua, 'transitionend', {
373
928
  'node': $heihei,
374
- 'nodeContainer': $nodeContainer,
929
+ 'nodeContainer': nodeContainerEl,
375
930
  'direction': undefined,
376
931
  'animatedNodes': $tiehua
377
932
  }, spy.bind(oc));
378
- $tiehua.trigger('transitionend');
933
+ triggerEvent($tiehua, 'transitionend');
379
934
  spy.should.have.been.called;
380
- $tiehua.is('.slide-right:not(.sliding)').should.be.true;
381
- $dandan.is('.slide-up:not(.sliding, .slide-right)').should.be.true;
382
- $pangpang.is('.slide-left:not(.sibling)').should.be.true;
383
- $sandan.is('.slide-up:not(.sliding, .slide-left)').should.be.true;
384
- $nodeContainer.siblings('.hidden').should.lengthOf(2);
385
- $nodeContainer.siblings().find('.nodes.hidden').should.lengthOf(2);
935
+ matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
936
+ matchesSelector($dandan, '.slide-up:not(.sliding, .slide-right)').should.be.true;
937
+ matchesSelector($pangpang, '.slide-left:not(.sibling)').should.be.true;
938
+ matchesSelector($sandan, '.slide-up:not(.sliding, .slide-left)').should.be.true;
939
+ siblingElements(nodeContainerEl, '.hidden').should.lengthOf(2);
940
+ siblingElements(nodeContainerEl).reduce(function (count, siblingEl) {
941
+ return count + queryAll('.nodes.hidden', siblingEl).length;
942
+ }, 0).should.equal(2);
386
943
  spy2.should.have.been.calledWith($heihei);
387
944
  spy3.should.not.have.been.called;
388
945
  });
@@ -390,55 +947,1618 @@ describe('orgchart -- unit tests', function () {
390
947
 
391
948
  context('when invoking transitionend event with specifying direction', function () {
392
949
  it('clean up final classList for left side hidden siblings', function () {
393
- var spy = sinon.spy(oc, 'hideSiblingsEnd');
394
- var spy2 = sinon.spy(oc, 'isInAction');
395
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
396
- var $nodeContainer = $heihei.closest('.hierarchy');
950
+ const spy = sinon.spy(oc, 'hideSiblingsEnd');
951
+ const spy2 = sinon.spy(oc, 'isInAction');
952
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
953
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
954
+
397
955
  oc.hideSiblings($heihei, 'left');
398
- $tiehua.one('transitionend', {
956
+
957
+ setOneTimeListener($tiehua, 'transitionend', {
399
958
  'node': $heihei,
400
- 'nodeContainer': $nodeContainer,
959
+ 'nodeContainer': nodeContainerEl,
401
960
  'direction': 'left',
402
961
  'animatedNodes': $tiehua
403
962
  }, spy.bind(oc));
404
- $tiehua.trigger('transitionend');
963
+ triggerEvent($tiehua, 'transitionend');
405
964
  spy.should.have.been.called;
406
- $tiehua.is('.slide-right:not(.sliding)').should.be.true;
407
- $dandan.is('.slide-up:not(.slide-right)').should.be.true;
408
- $pangpang.is('.slide-left').should.be.false;
409
- $sandan.is('.slide-up').should.be.false;
410
- $nodeContainer.prevAll('.hidden').should.lengthOf(1);
411
- $nodeContainer.prevAll().find('.nodes.hidden').should.lengthOf(1);
412
- $nodeContainer.nextAll().find('.nodes.hidden').should.lengthOf(0);
965
+ matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
966
+ matchesSelector($dandan, '.slide-up:not(.slide-right)').should.be.true;
967
+ matchesSelector($pangpang, '.slide-left').should.be.false;
968
+ matchesSelector($sandan, '.slide-up').should.be.false;
969
+ previousSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
970
+ previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
971
+ return count + queryAll('.nodes.hidden', siblingEl).length;
972
+ }, 0).should.equal(1);
973
+ nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
974
+ return count + queryAll('.nodes.hidden', siblingEl).length;
975
+ }, 0).should.equal(0);
413
976
  spy2.should.have.been.calledWith($heihei);
414
977
  spy3.should.not.have.been.called;
415
978
  });
416
979
 
417
980
  it('clean up final classList for right side hidden siblings', function () {
418
- var spy = sinon.spy(oc, 'hideSiblingsEnd');
419
- var spy2 = sinon.spy(oc, 'isInAction');
420
- var spy3 = sinon.spy(oc, 'switchVerticalArrow');
421
- var $nodeContainer = $heihei.closest('.hierarchy');
981
+ const spy2 = sinon.spy(oc, 'isInAction');
982
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
983
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
984
+
422
985
  oc.hideSiblings($heihei, 'right');
423
- $tiehua.one('transitionend', {
424
- 'node': $heihei,
425
- 'nodeContainer': $nodeContainer,
426
- 'direction': 'right',
427
- 'animatedNodes': $tiehua
428
- }, spy.bind(oc));
429
- $tiehua.trigger('transitionend');
430
- spy.should.have.been.called;
431
- $tiehua.is('.slide-right').should.be.false;
432
- $dandan.is('.slide-up').should.be.false;
433
- $pangpang.is('.slide-left:not(.slidiing)').should.be.true;
434
- $sandan.is('.slide-up:not(.slide-left)').should.be.true;
435
- $nodeContainer.nextAll('.hidden').should.lengthOf(1);
436
- $nodeContainer.nextAll().find('.nodes.hidden').should.lengthOf(1);
437
- $nodeContainer.prevAll().find('.nodes.hidden').should.lengthOf(0);
986
+
987
+ oc.hideSiblingsEnd({
988
+ node: $heihei,
989
+ nodeContainer: nodeContainerEl,
990
+ direction: 'right',
991
+ animatedNodes: $tiehua
992
+ });
993
+ matchesSelector($tiehua, '.slide-right').should.be.false;
994
+ matchesSelector($dandan, '.slide-up').should.be.false;
995
+ matchesSelector($pangpang, '.slide-left:not(.slidiing)').should.be.true;
996
+ matchesSelector($sandan, '.slide-up:not(.slide-left)').should.be.true;
997
+ nextSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
998
+ nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
999
+ return count + queryAll('.nodes.hidden', siblingEl).length;
1000
+ }, 0).should.equal(1);
1001
+ previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
1002
+ return count + queryAll('.nodes.hidden', siblingEl).length;
1003
+ }, 0).should.equal(0);
438
1004
  spy2.should.have.been.calledWith($heihei);
439
1005
  spy3.should.not.have.been.called;
440
1006
  });
1007
+
1008
+ it('accepts direct node, container, and animated payloads', function () {
1009
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1010
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
1011
+
1012
+ oc.hideSiblings($heihei, 'left');
1013
+ addClasses($tiehua, 'sliding');
1014
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
1015
+ query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
1016
+
1017
+ oc.hideSiblingsEnd({
1018
+ node: $heihei,
1019
+ nodeContainer: nodeContainerEl,
1020
+ direction: 'left',
1021
+ animatedNodes: [$tiehua]
1022
+ });
1023
+
1024
+ $tiehua.classList.contains('sliding').should.equal(false);
1025
+ nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
1026
+ horizontalSpy.should.have.been.calledWith($heihei);
1027
+
1028
+ horizontalSpy.restore();
1029
+ });
1030
+
1031
+ it('accepts wrapped native node, container, and animated payloads', function () {
1032
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1033
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
1034
+
1035
+ oc.hideSiblings($heihei, 'left');
1036
+ addClasses($tiehua, 'sliding');
1037
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
1038
+ query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
1039
+
1040
+ oc.hideSiblingsEnd({
1041
+ originalEvent: {
1042
+ node: $heihei,
1043
+ nodeContainer: nodeContainerEl,
1044
+ direction: 'left',
1045
+ animatedNodes: [$tiehua]
1046
+ }
1047
+ });
1048
+
1049
+ $tiehua.classList.contains('sliding').should.equal(false);
1050
+ nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
1051
+ horizontalSpy.should.have.been.calledWith($heihei);
1052
+
1053
+ horizontalSpy.restore();
1054
+ });
441
1055
  });
442
1056
  });
443
1057
 
444
- });
1058
+ it('nodeEnterLeaveHandler() accepts a native node payload for relationship checks', function () {
1059
+ const spy = sinon.spy(oc, 'switchHorizontalArrow');
1060
+ oc.nodeEnterLeaveHandler({ type: 'mouseenter', node: $sumiao });
1061
+ spy.should.have.been.calledWith($sumiao);
1062
+ });
1063
+
1064
+ it('nodeEnterLeaveHandler() removes arrow classes from a native node payload on mouseleave', function () {
1065
+ const nodeEl = $sumiao;
1066
+
1067
+ Array.from(nodeEl.querySelectorAll('.edge')).forEach(function (edgeEl) {
1068
+ edgeEl.classList.add(
1069
+ oc.options.icons.expandToUp,
1070
+ oc.options.icons.collapseToDown,
1071
+ oc.options.icons.collapseToLeft,
1072
+ oc.options.icons.expandToRight
1073
+ );
1074
+ });
1075
+
1076
+ oc.nodeEnterLeaveHandler({ type: 'mouseleave', node: nodeEl });
1077
+
1078
+ Array.from(nodeEl.querySelectorAll('.edge')).forEach(function (edgeEl) {
1079
+ edgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
1080
+ edgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(false);
1081
+ edgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
1082
+ edgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
1083
+ });
1084
+ });
1085
+
1086
+ it('nodeEnterLeaveHandler() accepts wrapped native node payloads', function () {
1087
+ const spy = sinon.spy(oc, 'switchHorizontalArrow');
1088
+
1089
+ oc.nodeEnterLeaveHandler({
1090
+ originalEvent: {
1091
+ type: 'mouseenter',
1092
+ currentTarget: $sumiao
1093
+ }
1094
+ });
1095
+
1096
+ spy.should.have.been.calledWith($sumiao);
1097
+ spy.restore();
1098
+ });
1099
+
1100
+ it('HideFirstParentEnd() accepts a native top edge payload', function () {
1101
+ const topEdgeEl = query('.topEdge', $heihei);
1102
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
1103
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1104
+
1105
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
1106
+ oc.HideFirstParentEnd({ topEdge: topEdgeEl });
1107
+
1108
+ verticalSpy.should.have.been.calledWith(topEdgeEl);
1109
+ horizontalSpy.should.have.been.calledWith($heihei);
1110
+
1111
+ verticalSpy.restore();
1112
+ horizontalSpy.restore();
1113
+ });
1114
+
1115
+ it('HideFirstParentEnd() accepts a wrapped native top edge payload', function () {
1116
+ const topEdgeEl = query('.topEdge', $heihei);
1117
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
1118
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1119
+
1120
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
1121
+ oc.HideFirstParentEnd({
1122
+ originalEvent: {
1123
+ topEdge: topEdgeEl
1124
+ }
1125
+ });
1126
+
1127
+ verticalSpy.should.have.been.calledWith(topEdgeEl);
1128
+ horizontalSpy.should.have.been.calledWith($heihei);
1129
+
1130
+ verticalSpy.restore();
1131
+ horizontalSpy.restore();
1132
+ });
1133
+
1134
+ it('nodeClickHandler() focuses the native node payload and clears previous focus', function () {
1135
+ addClasses($bomiao, 'focused');
1136
+
1137
+ oc.nodeClickHandler({ node: $sumiao });
1138
+
1139
+ $bomiao.classList.contains('focused').should.equal(false);
1140
+ $sumiao.classList.contains('focused').should.equal(true);
1141
+ });
1142
+
1143
+ it('nodeClickHandler() focuses a wrapped native node payload', function () {
1144
+ addClasses($bomiao, 'focused');
1145
+
1146
+ oc.nodeClickHandler({
1147
+ originalEvent: {
1148
+ currentTarget: $sumiao
1149
+ }
1150
+ });
1151
+
1152
+ $bomiao.classList.contains('focused').should.equal(false);
1153
+ $sumiao.classList.contains('focused').should.equal(true);
1154
+ });
1155
+
1156
+ it('init() clears focused nodes when clicking the chart background', function () {
1157
+ addClasses($bomiao, 'focused');
1158
+ addClasses($sumiao, 'focused');
1159
+
1160
+ oc.chart.dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
1161
+
1162
+ $bomiao.classList.contains('focused').should.equal(false);
1163
+ $sumiao.classList.contains('focused').should.equal(false);
1164
+ });
1165
+
1166
+ it('init() clears focused nodes from a wrapped native background click payload', function () {
1167
+ const wrappedClickEvent = new window.Event('click', { bubbles: true });
1168
+
1169
+ addClasses($bomiao, 'focused');
1170
+ addClasses($sumiao, 'focused');
1171
+
1172
+ wrappedClickEvent.originalEvent = {
1173
+ target: oc.chart
1174
+ };
1175
+ oc.chart.dispatchEvent(wrappedClickEvent);
1176
+
1177
+ $bomiao.classList.contains('focused').should.equal(false);
1178
+ $sumiao.classList.contains('focused').should.equal(false);
1179
+ });
1180
+
1181
+ it('init() appends the export button only once', function () {
1182
+ let oc2;
1183
+
1184
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1185
+ exportButtonEl.remove();
1186
+ });
1187
+
1188
+ oc2 = new OrgChart({
1189
+ chartContainer: '#chart-container',
1190
+ data: ds,
1191
+ exportButton: true
1192
+ });
1193
+
1194
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1195
+
1196
+ oc2.init();
1197
+
1198
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1199
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1200
+ exportButtonEl.remove();
1201
+ });
1202
+ });
1203
+
1204
+ it('attachExportButton() creates a native export button that calls export()', function () {
1205
+ const exportStub = sinon.stub(oc, 'export');
1206
+
1207
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1208
+ exportButtonEl.remove();
1209
+ });
1210
+
1211
+ oc.attachExportButton();
1212
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1213
+ document.querySelector('.oc-export-btn').dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
1214
+ exportStub.should.have.been.calledOnce;
1215
+
1216
+ exportStub.restore();
1217
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1218
+ exportButtonEl.remove();
1219
+ });
1220
+ });
1221
+
1222
+ it('topEdgeClickHandler() passes a native node to hideParent()', function () {
1223
+ const spy = sinon.spy(oc, 'hideParent');
1224
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1225
+
1226
+ oc.topEdgeClickHandler({ edge: query('.topEdge', $heihei), node: $heihei });
1227
+
1228
+ spy.should.have.been.calledWith($heihei);
1229
+ spy2.should.have.been.called;
1230
+ });
1231
+
1232
+ it('topEdgeClickHandler() accepts wrapped native edge payloads', function () {
1233
+ const spy = sinon.spy(oc, 'hideParent');
1234
+
1235
+ oc.topEdgeClickHandler({
1236
+ originalEvent: {
1237
+ currentTarget: $heihei,
1238
+ target: query('.topEdge', $heihei)
1239
+ }
1240
+ });
1241
+
1242
+ spy.should.have.been.calledWith($heihei);
1243
+ spy.restore();
1244
+ });
1245
+
1246
+ it('bottomEdgeClickHandler() passes a native node to hideChildren()', function () {
1247
+ const spy = sinon.spy(oc, 'hideChildren');
1248
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1249
+
1250
+ oc.bottomEdgeClickHandler({ edge: query('.bottomEdge', $sumiao), node: $sumiao });
1251
+
1252
+ spy.should.have.been.calledWith($sumiao);
1253
+ spy2.should.have.been.called;
1254
+ });
1255
+
1256
+ it('bottomEdgeClickHandler() accepts wrapped native node payloads', function () {
1257
+ const spy = sinon.spy(oc, 'hideChildren');
1258
+
1259
+ oc.bottomEdgeClickHandler({
1260
+ originalEvent: {
1261
+ currentTarget: $sumiao
1262
+ }
1263
+ });
1264
+
1265
+ spy.should.have.been.calledWith($sumiao);
1266
+ spy.restore();
1267
+ });
1268
+
1269
+ it('hEdgeClickHandler() passes a native node to hideSiblings()', function () {
1270
+ const spy = sinon.spy(oc, 'hideSiblings');
1271
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1272
+
1273
+ oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
1274
+
1275
+ spy.should.have.been.calledWith($sumiao);
1276
+ spy2.should.have.been.called;
1277
+ });
1278
+
1279
+ it('hEdgeClickHandler() accepts wrapped native edge payloads', function () {
1280
+ const spy = sinon.spy(oc, 'hideSiblings');
1281
+
1282
+ oc.hEdgeClickHandler({
1283
+ originalEvent: {
1284
+ currentTarget: $sumiao,
1285
+ target: query('.leftEdge', $sumiao)
1286
+ }
1287
+ });
1288
+
1289
+ spy.should.have.been.calledWith($sumiao);
1290
+ spy.restore();
1291
+ });
1292
+
1293
+ it('hEdgeClickHandler() restores the collapsed parent after topEdgeClickHandler()', function () {
1294
+ oc.topEdgeClickHandler({ edge: query('.topEdge', $sumiao), node: $sumiao });
1295
+ triggerEvent($bomiao, 'transitionend');
1296
+ triggerEvent($laolao, 'transitionend');
1297
+
1298
+ oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
1299
+
1300
+ closestElement($sumiao, '.hierarchy').classList.contains('isAncestorsCollapsed').should.equal(false);
1301
+ $laolao.classList.contains('hidden').should.equal(false);
1302
+ $laolao.classList.contains('slide-down').should.equal(false);
1303
+ $laolao.classList.contains('sliding').should.equal(true);
1304
+ });
1305
+
1306
+ it('startLoading() and endLoading() accept a native edge node', function () {
1307
+ const edgeEl = query('.bottomEdge', $sumiao);
1308
+
1309
+ oc.startLoading(edgeEl).should.be.true;
1310
+ matchesSelector(edgeEl, '.hidden').should.be.true;
1311
+ queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(1);
1312
+ getState(oc.chart, 'inAjax').should.be.true;
1313
+
1314
+ oc.endLoading(edgeEl);
1315
+ matchesSelector(edgeEl, '.hidden').should.be.false;
1316
+ queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(0);
1317
+ getState(oc.chart, 'inAjax').should.be.false;
1318
+ });
1319
+
1320
+ it('startLoading() and endLoading() toggle native export buttons', function () {
1321
+ const edgeEl = query('.bottomEdge', $sumiao);
1322
+ const exportButtonEl = document.createElement('button');
1323
+
1324
+ exportButtonEl.className = 'oc-export-btn';
1325
+ document.body.appendChild(exportButtonEl);
1326
+
1327
+ oc.startLoading(edgeEl);
1328
+ exportButtonEl.disabled.should.equal(true);
1329
+
1330
+ oc.endLoading(edgeEl);
1331
+ exportButtonEl.disabled.should.equal(false);
1332
+
1333
+ exportButtonEl.remove();
1334
+ });
1335
+
1336
+ it('stopAjax() accepts a native node level element', function () {
1337
+ const nodeLevelEl = oc.getChildren($sumiao)[0].closest('.nodes');
1338
+ const spinnerEl = document.createElement('i');
1339
+
1340
+ spinnerEl.className = 'spinner';
1341
+ nodeLevelEl.appendChild(spinnerEl);
1342
+ setState(oc.chart, 'inAjax', true);
1343
+
1344
+ oc.stopAjax(nodeLevelEl);
1345
+
1346
+ getState(oc.chart, 'inAjax').should.be.false;
1347
+ spinnerEl.remove();
1348
+ });
1349
+
1350
+ it('backToLooseHandler() and backToCompactHandler() toggle compact mode classes', function () {
1351
+ const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
1352
+
1353
+ $container.appendChild(compactNodeEl);
1354
+
1355
+ oc.backToLooseHandler({ compactNode: compactNodeEl });
1356
+ matchesSelector(compactNodeEl, '.looseMode').should.be.true;
1357
+ matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.true;
1358
+ matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.false;
1359
+
1360
+ oc.backToCompactHandler({ compactNode: compactNodeEl });
1361
+ matchesSelector(compactNodeEl, '.looseMode').should.be.false;
1362
+ matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.true;
1363
+ matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.false;
1364
+ });
1365
+
1366
+ it('backToLooseHandler() and backToCompactHandler() accept wrapped native compact-node payloads', function () {
1367
+ const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
1368
+
1369
+ $container.appendChild(compactNodeEl);
1370
+
1371
+ oc.backToLooseHandler({
1372
+ originalEvent: {
1373
+ currentTarget: compactNodeEl
1374
+ }
1375
+ });
1376
+ matchesSelector(compactNodeEl, '.looseMode').should.be.true;
1377
+
1378
+ oc.backToCompactHandler({
1379
+ originalEvent: {
1380
+ currentTarget: compactNodeEl
1381
+ }
1382
+ });
1383
+ matchesSelector(compactNodeEl, '.looseMode').should.be.false;
1384
+ });
1385
+
1386
+ it('toggleVNodes() expands vertical descendants from a native toggle button payload', function () {
1387
+ const spy = sinon.spy(oc, 'repaint');
1388
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1389
+ const descWrapperEl = createElementFromHtml('<ul class="nodes hidden"><li class="hierarchy"><div class="node slide-up">child</div></li></ul>');
1390
+ let childNodeEl;
1391
+
1392
+ $container.appendChild(toggleHostEl);
1393
+ $container.appendChild(descWrapperEl);
1394
+ childNodeEl = query('.node', descWrapperEl);
1395
+
1396
+ oc.toggleVNodes({ toggleButton: query('.toggleBtn', toggleHostEl) });
1397
+
1398
+ spy.should.have.been.calledWith(childNodeEl);
1399
+ matchesSelector(descWrapperEl, '.hidden').should.be.false;
1400
+ matchesSelector(childNodeEl, '.sliding').should.be.true;
1401
+ matchesSelector(childNodeEl, '.slide-up').should.be.false;
1402
+ });
1403
+
1404
+ it('toggleVNodes() accepts a wrapped native toggle button payload', function () {
1405
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1406
+ const descWrapperEl = createElementFromHtml('<ul class="nodes hidden"><li class="hierarchy"><div class="node slide-up">child</div></li></ul>');
1407
+
1408
+ $container.appendChild(toggleHostEl);
1409
+ $container.appendChild(descWrapperEl);
1410
+
1411
+ oc.toggleVNodes({
1412
+ originalEvent: {
1413
+ target: query('.toggleBtn', toggleHostEl)
1414
+ }
1415
+ });
1416
+
1417
+ matchesSelector(descWrapperEl, '.hidden').should.be.false;
1418
+ });
1419
+
1420
+ it('toggleVNodes() collapses vertical descendants from a native toggle button payload', function () {
1421
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1422
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node">child<i class="toggleBtn oci-plus-square oci-minus-square"></i></div></li></ul>');
1423
+ let nestedToggleBtnEl;
1424
+ let childNodeEl;
1425
+
1426
+ $container.appendChild(toggleHostEl);
1427
+ $container.appendChild(descWrapperEl);
1428
+ nestedToggleBtnEl = query('.node .toggleBtn', descWrapperEl);
1429
+ childNodeEl = query('.node', descWrapperEl);
1430
+
1431
+ oc.toggleVNodes({ toggleButton: query('.toggleBtn', toggleHostEl) });
1432
+
1433
+ matchesSelector(childNodeEl, '.sliding.slide-up').should.be.true;
1434
+ nestedToggleBtnEl.classList.contains('oci-plus-square').should.be.false;
1435
+ nestedToggleBtnEl.classList.contains('oci-minus-square').should.be.false;
1436
+ });
1437
+
1438
+ it('expandVNodesEnd() accepts direct vNodes payloads', function () {
1439
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1440
+ const childNodeEl = query('.node', descWrapperEl);
1441
+
1442
+ $container.appendChild(descWrapperEl);
1443
+
1444
+ oc.expandVNodesEnd({ vNodes: [childNodeEl] });
1445
+
1446
+ childNodeEl.classList.contains('sliding').should.equal(false);
1447
+ });
1448
+
1449
+ it('expandVNodesEnd() accepts wrapped native vNodes payloads', function () {
1450
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1451
+ const childNodeEl = query('.node', descWrapperEl);
1452
+
1453
+ $container.appendChild(descWrapperEl);
1454
+
1455
+ oc.expandVNodesEnd({
1456
+ originalEvent: {
1457
+ vNodes: [childNodeEl]
1458
+ }
1459
+ });
1460
+
1461
+ childNodeEl.classList.contains('sliding').should.equal(false);
1462
+ });
1463
+
1464
+ it('collapseVNodesEnd() accepts direct vNodes payloads', function () {
1465
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1466
+ const childNodeEl = query('.node', descWrapperEl);
1467
+
1468
+ $container.appendChild(descWrapperEl);
1469
+
1470
+ oc.collapseVNodesEnd({ vNodes: [childNodeEl] });
1471
+
1472
+ childNodeEl.classList.contains('sliding').should.equal(false);
1473
+ descWrapperEl.classList.contains('hidden').should.equal(true);
1474
+ });
1475
+
1476
+ it('collapseVNodesEnd() accepts wrapped native vNodes payloads', function () {
1477
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1478
+ const childNodeEl = query('.node', descWrapperEl);
1479
+
1480
+ $container.appendChild(descWrapperEl);
1481
+
1482
+ oc.collapseVNodesEnd({
1483
+ originalEvent: {
1484
+ vNodes: [childNodeEl]
1485
+ }
1486
+ });
1487
+
1488
+ childNodeEl.classList.contains('sliding').should.equal(false);
1489
+ descWrapperEl.classList.contains('hidden').should.equal(true);
1490
+ });
1491
+
1492
+ it('filterAllowedDropNodes() accepts a native dragged node', function () {
1493
+ oc.filterAllowedDropNodes($heihei);
1494
+
1495
+ getState(oc.chart, 'dragged').should.equal($heihei);
1496
+ $laolao.classList.contains('allowedDrop').should.be.true;
1497
+ queryAll('.allowedDrop', oc.chart).length.should.be.above(0);
1498
+ });
1499
+
1500
+ it('filterAllowedDropNodes() passes native node arguments to dropCriteria', function () {
1501
+ const dropCriteria = sinon.stub().returns(true);
1502
+
1503
+ oc.options.dropCriteria = dropCriteria;
1504
+
1505
+ oc.filterAllowedDropNodes($heihei);
1506
+
1507
+ dropCriteria.should.have.been.called;
1508
+ dropCriteria.firstCall.args[0].should.equal($heihei);
1509
+ dropCriteria.firstCall.args[1].should.equal($sumiao);
1510
+ dropCriteria.firstCall.args[2].should.have.property('nodeType', 1);
1511
+ });
1512
+
1513
+ it('dragstartHandler() passes a native dragged node payload to filterAllowedDropNodes()', function () {
1514
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1515
+ const dataTransfer = { setData: sinon.spy() };
1516
+
1517
+ oc.dragstartHandler({ draggedNode: $heihei, originalEvent: { dataTransfer: dataTransfer } });
1518
+
1519
+ dataTransfer.setData.should.have.been.called;
1520
+ spy.should.have.been.calledWith($heihei);
1521
+ });
1522
+
1523
+ it('dragstartHandler() accepts a wrapped native dragged node payload', function () {
1524
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1525
+ const dataTransfer = { setData: sinon.spy() };
1526
+
1527
+ oc.dragstartHandler({
1528
+ originalEvent: {
1529
+ target: $heihei,
1530
+ dataTransfer: dataTransfer
1531
+ }
1532
+ });
1533
+
1534
+ dataTransfer.setData.should.have.been.called;
1535
+ spy.should.have.been.calledWith($heihei);
1536
+ spy.restore();
1537
+ });
1538
+
1539
+ it('createGhostNode() accepts a native source node payload and creates a ghost image', function () {
1540
+ const setDragImageSpy = sinon.spy();
1541
+ const sourceNodeEl = $heihei;
1542
+
1543
+ oc.chart.style.transform = 'matrix(1, 0, 0, 1, 10, 20)';
1544
+ sourceNodeEl.getBoundingClientRect = function () {
1545
+ return { width: 120, height: 40, left: 10, top: 20 };
1546
+ };
1547
+
1548
+ oc.createGhostNode({
1549
+ sourceNode: sourceNodeEl,
1550
+ originalEvent: {
1551
+ offsetX: 5,
1552
+ offsetY: 6,
1553
+ dataTransfer: { setDragImage: setDragImageSpy }
1554
+ }
1555
+ });
1556
+
1557
+ const ghostNodeEl = oc.chart.querySelector('.ghost-node');
1558
+ should.exist(ghostNodeEl);
1559
+ ghostNodeEl.getAttribute('width').should.equal('120');
1560
+ ghostNodeEl.getAttribute('height').should.equal('40');
1561
+ setDragImageSpy.should.have.been.calledOnce;
1562
+ setDragImageSpy.firstCall.args[0].should.equal(ghostNodeEl);
1563
+ });
1564
+
1565
+ it('createGhostNode() accepts a wrapped native source node payload', function () {
1566
+ const setDragImageSpy = sinon.spy();
1567
+ const sourceNodeEl = $heihei;
1568
+
1569
+ oc.chart.style.transform = 'matrix(1, 0, 0, 1, 10, 20)';
1570
+ sourceNodeEl.getBoundingClientRect = function () {
1571
+ return { width: 120, height: 40, left: 10, top: 20 };
1572
+ };
1573
+
1574
+ oc.createGhostNode({
1575
+ originalEvent: {
1576
+ target: sourceNodeEl,
1577
+ offsetX: 5,
1578
+ offsetY: 6,
1579
+ dataTransfer: { setDragImage: setDragImageSpy }
1580
+ }
1581
+ });
1582
+
1583
+ should.exist(oc.chart.querySelector('.ghost-node'));
1584
+ setDragImageSpy.should.have.been.calledOnce;
1585
+ });
1586
+
1587
+ it('dragoverHandler() accepts a native drop zone payload', function () {
1588
+ const disallowedEvent = {
1589
+ dropZone: $bomiao,
1590
+ originalEvent: { dataTransfer: { dropEffect: 'move' } },
1591
+ preventDefault: sinon.spy()
1592
+ };
1593
+ disallowedEvent.originalEvent.preventDefault = disallowedEvent.preventDefault;
1594
+ const allowedEvent = {
1595
+ dropZone: addClasses($hongmiao, 'allowedDrop'),
1596
+ originalEvent: { dataTransfer: { dropEffect: 'move' } },
1597
+ preventDefault: sinon.spy()
1598
+ };
1599
+ allowedEvent.originalEvent.preventDefault = allowedEvent.preventDefault;
1600
+
1601
+ oc.dragoverHandler(disallowedEvent);
1602
+ oc.dragoverHandler(allowedEvent);
1603
+
1604
+ disallowedEvent.originalEvent.dataTransfer.dropEffect.should.equal('none');
1605
+ disallowedEvent.preventDefault.should.not.have.been.called;
1606
+ allowedEvent.preventDefault.should.have.been.calledOnce;
1607
+ });
1608
+
1609
+ it('dragoverHandler() accepts wrapped native drop-zone payloads', function () {
1610
+ const wrappedEvent = {
1611
+ originalEvent: {
1612
+ currentTarget: addClasses($hongmiao, 'allowedDrop'),
1613
+ dataTransfer: { dropEffect: 'move' },
1614
+ preventDefault: sinon.spy()
1615
+ }
1616
+ };
1617
+
1618
+ oc.dragoverHandler(wrappedEvent);
1619
+
1620
+ wrappedEvent.originalEvent.preventDefault.should.have.been.calledOnce;
1621
+ });
1622
+
1623
+ it('dragendHandler() removes allowedDrop from all nodes', function () {
1624
+ addClasses($bomiao, 'allowedDrop');
1625
+ addClasses($hongmiao, 'allowedDrop');
1626
+
1627
+ oc.dragendHandler({});
1628
+
1629
+ queryAll('.allowedDrop', oc.chart).should.have.lengthOf(0);
1630
+ });
1631
+
1632
+ it('dropHandler() accepts a native drop zone for non-node drags', async function () {
1633
+ const spy = sinon.spy();
1634
+ const foreignEl = createElementFromHtml('<div class="foreign-item"></div>');
1635
+
1636
+ oc.chart.addEventListener('otherdropped.orgchart', spy);
1637
+ setState(oc.chart, 'dragged', foreignEl);
1638
+
1639
+ await oc.dropHandler({ dropZone: $bomiao });
1640
+
1641
+ spy.should.have.been.calledOnce;
1642
+ spy.firstCall.args[0].type.should.equal('otherdropped.orgchart');
1643
+ spy.firstCall.args[0].detail.dropZone.should.equal($bomiao);
1644
+ });
1645
+
1646
+ it('dropHandler() accepts a wrapped native drop zone for non-node drags', async function () {
1647
+ const spy = sinon.spy();
1648
+ const foreignEl = createElementFromHtml('<div class="foreign-item"></div>');
1649
+
1650
+ oc.chart.addEventListener('otherdropped.orgchart', spy);
1651
+ setState(oc.chart, 'dragged', foreignEl);
1652
+
1653
+ await oc.dropHandler({
1654
+ originalEvent: {
1655
+ currentTarget: $bomiao
1656
+ }
1657
+ });
1658
+
1659
+ spy.should.have.been.calledOnce;
1660
+ spy.firstCall.args[0].detail.dropZone.should.equal($bomiao);
1661
+ });
1662
+
1663
+ it('dropHandler() accepts a native drop zone for node drags', async function () {
1664
+ const originalJSONDigger = global.JSONDigger;
1665
+
1666
+ global.JSONDigger = function () {
1667
+ return {
1668
+ findOneNode: function () {
1669
+ return null;
1670
+ }
1671
+ };
1672
+ };
1673
+ setState(oc.chart, 'dragged', $bomiao);
1674
+ addClasses($hongmiao, 'allowedDrop');
1675
+
1676
+ await oc.dropHandler({ dropZone: $hongmiao });
1677
+
1678
+ queryAll('#n2', firstSibling($hongmiao, '.nodes')).should.lengthOf(1);
1679
+ childElements(firstSibling($laolao, '.nodes'), '.hierarchy').should.lengthOf(2);
1680
+ global.JSONDigger = originalJSONDigger;
1681
+ });
1682
+
1683
+ it('dropHandler() keeps nodedrop payloads as native nodes', async function () {
1684
+ const originalJSONDigger = global.JSONDigger;
1685
+ const spy = sinon.spy();
1686
+
1687
+ global.JSONDigger = function () {
1688
+ return {
1689
+ findOneNode: function () {
1690
+ return null;
1691
+ }
1692
+ };
1693
+ };
1694
+ oc.chart.addEventListener('nodedrop.orgchart', spy);
1695
+ setState(oc.chart, 'dragged', $bomiao);
1696
+ addClasses($hongmiao, 'allowedDrop');
1697
+
1698
+ await oc.dropHandler({ dropZone: $hongmiao });
1699
+
1700
+ spy.should.have.been.calledOnce;
1701
+ spy.firstCall.args[0].type.should.equal('nodedrop.orgchart');
1702
+ spy.firstCall.args[0].detail.draggedNode.should.equal($bomiao);
1703
+ spy.firstCall.args[0].detail.dragZone.should.equal($laolao);
1704
+ spy.firstCall.args[0].detail.dropZone.should.equal($hongmiao);
1705
+ global.JSONDigger = originalJSONDigger;
1706
+ });
1707
+
1708
+ it('dropHandler() removes source child container when dragged node was the only child', async function () {
1709
+ const originalJSONDigger = global.JSONDigger;
1710
+
1711
+ global.JSONDigger = function () {
1712
+ return {
1713
+ findOneNode: function () {
1714
+ return null;
1715
+ }
1716
+ };
1717
+ };
1718
+ setState(oc.chart, 'dragged', $dandan);
1719
+ addClasses($hongmiao, 'allowedDrop');
1720
+
1721
+ await oc.dropHandler({ dropZone: $hongmiao });
1722
+
1723
+ queryAll('#n8', firstSibling($hongmiao, '.nodes')).should.lengthOf(1);
1724
+ siblingElements($tiehua, '.nodes').should.lengthOf(0);
1725
+ childElements($tiehua, '.bottomEdge, .parentNodeSymbol').should.lengthOf(0);
1726
+ global.JSONDigger = originalJSONDigger;
1727
+ });
1728
+
1729
+ it('touchmoveHandler() passes a native dragged node payload to filterAllowedDropNodes()', function () {
1730
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1731
+ const createDragImageStub = sinon.stub(oc, 'createDragImage').returns(document.createElement('div'));
1732
+ const moveDragImageStub = sinon.stub(oc, 'moveDragImage');
1733
+ const originalElementFromPoint = document.elementFromPoint;
1734
+
1735
+ document.elementFromPoint = function () {
1736
+ return $bomiao;
1737
+ };
1738
+
1739
+ oc.touchHandled = true;
1740
+ oc.touchMoved = false;
1741
+ oc.touchmoveHandler({
1742
+ draggedNode: $heihei,
1743
+ touches: [{ clientX: 0, clientY: 0 }],
1744
+ preventDefault: function () {}
1745
+ });
1746
+
1747
+ spy.should.have.been.calledWith($heihei);
1748
+ createDragImageStub.should.have.been.called;
1749
+ moveDragImageStub.should.have.been.called;
1750
+ oc.touchTargetNode.should.equal($bomiao);
1751
+
1752
+ document.elementFromPoint = originalElementFromPoint;
1753
+ moveDragImageStub.restore();
1754
+ createDragImageStub.restore();
1755
+ });
1756
+
1757
+ it('touchmoveHandler() accepts wrapped native touch payloads', function () {
1758
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1759
+ const createDragImageStub = sinon.stub(oc, 'createDragImage').returns(document.createElement('div'));
1760
+ const moveDragImageStub = sinon.stub(oc, 'moveDragImage');
1761
+ const originalElementFromPoint = document.elementFromPoint;
1762
+
1763
+ document.elementFromPoint = function () {
1764
+ return $bomiao;
1765
+ };
1766
+
1767
+ oc.touchHandled = true;
1768
+ oc.touchMoved = false;
1769
+ oc.touchmoveHandler({
1770
+ originalEvent: {
1771
+ currentTarget: $heihei,
1772
+ touches: [{ clientX: 0, clientY: 0 }],
1773
+ preventDefault: function () {}
1774
+ }
1775
+ });
1776
+
1777
+ spy.should.have.been.calledWith($heihei);
1778
+ createDragImageStub.should.have.been.called;
1779
+ moveDragImageStub.should.have.been.called;
1780
+ oc.touchTargetNode.should.equal($bomiao);
1781
+
1782
+ document.elementFromPoint = originalElementFromPoint;
1783
+ moveDragImageStub.restore();
1784
+ createDragImageStub.restore();
1785
+ spy.restore();
1786
+ });
1787
+
1788
+ it('touchstartHandler() accepts a wrapped native touch payload', function () {
1789
+ const preventDefaultSpy = sinon.spy();
1790
+
1791
+ oc.touchHandled = false;
1792
+ oc.touchMoved = true;
1793
+
1794
+ oc.touchstartHandler({
1795
+ originalEvent: {
1796
+ touches: [{ clientX: 0, clientY: 0 }],
1797
+ preventDefault: preventDefaultSpy
1798
+ }
1799
+ });
1800
+
1801
+ oc.touchHandled.should.equal(true);
1802
+ oc.touchMoved.should.equal(false);
1803
+ preventDefaultSpy.should.have.been.calledOnce;
1804
+ });
1805
+
1806
+ it('getTouchPoint() accepts a wrapped native touch payload', function () {
1807
+ const touchPoint = oc.getTouchPoint({
1808
+ originalEvent: {
1809
+ touches: [{ clientX: 12, clientY: 34 }]
1810
+ }
1811
+ });
1812
+
1813
+ touchPoint.x.should.equal(12);
1814
+ touchPoint.y.should.equal(34);
1815
+ });
1816
+
1817
+ it('touchendHandler() passes a native drop zone to dropHandler() after touch drag', function () {
1818
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1819
+ const dropHandlerStub = sinon.stub(oc, 'dropHandler');
1820
+ const dragendHandlerStub = sinon.stub(oc, 'dragendHandler');
1821
+
1822
+ oc.touchHandled = true;
1823
+ oc.touchMoved = true;
1824
+ oc.touchTargetNode = $bomiao;
1825
+
1826
+ oc.touchendHandler({});
1827
+
1828
+ destroyDragImageStub.should.have.been.calledOnce;
1829
+ dropHandlerStub.should.have.been.calledOnce;
1830
+ dropHandlerStub.firstCall.args[0].dropZone.should.equal($bomiao);
1831
+ dragendHandlerStub.should.have.been.calledOnce;
1832
+ should.equal(oc.touchTargetNode, null);
1833
+ oc.touchHandled.should.equal(false);
1834
+
1835
+ dragendHandlerStub.restore();
1836
+ dropHandlerStub.restore();
1837
+ destroyDragImageStub.restore();
1838
+ });
1839
+
1840
+ it('touchendHandler() dispatches a native click when touch did not move', function () {
1841
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1842
+ const clickSpy = sinon.spy();
1843
+ const targetEl = document.createElement('div');
1844
+
1845
+ targetEl.addEventListener('click', clickSpy);
1846
+ oc.touchHandled = true;
1847
+ oc.touchMoved = false;
1848
+
1849
+ oc.touchendHandler({
1850
+ target: targetEl,
1851
+ changedTouches: [{ screenX: 1, screenY: 2, clientX: 3, clientY: 4 }],
1852
+ ctrlKey: false,
1853
+ altKey: false,
1854
+ shiftKey: false,
1855
+ metaKey: false
1856
+ });
1857
+
1858
+ destroyDragImageStub.should.have.been.calledOnce;
1859
+ clickSpy.should.have.been.calledOnce;
1860
+ clickSpy.firstCall.args[0].type.should.equal('click');
1861
+ clickSpy.firstCall.args[0].clientX.should.equal(3);
1862
+ clickSpy.firstCall.args[0].clientY.should.equal(4);
1863
+ oc.touchHandled.should.equal(false);
1864
+
1865
+ destroyDragImageStub.restore();
1866
+ });
1867
+
1868
+ it('touchendHandler() accepts wrapped native touchend payloads', function () {
1869
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1870
+ const clickSpy = sinon.spy();
1871
+ const targetEl = document.createElement('div');
1872
+
1873
+ targetEl.addEventListener('click', clickSpy);
1874
+ oc.touchHandled = true;
1875
+ oc.touchMoved = false;
1876
+
1877
+ oc.touchendHandler({
1878
+ originalEvent: {
1879
+ target: targetEl,
1880
+ changedTouches: [{ screenX: 1, screenY: 2, clientX: 3, clientY: 4 }],
1881
+ ctrlKey: false,
1882
+ altKey: false,
1883
+ shiftKey: false,
1884
+ metaKey: false
1885
+ }
1886
+ });
1887
+
1888
+ destroyDragImageStub.should.have.been.calledOnce;
1889
+ clickSpy.should.have.been.calledOnce;
1890
+ oc.touchHandled.should.equal(false);
1891
+
1892
+ destroyDragImageStub.restore();
1893
+ });
1894
+
1895
+ it('getUpperLevel() and getLowerLevel() accept native nodes', function () {
1896
+ oc.getUpperLevel($laolao).should.equal(1);
1897
+ oc.getUpperLevel($dandan).should.equal(4);
1898
+ oc.getLowerLevel($laolao).should.equal(4);
1899
+ oc.getLowerLevel($dandan).should.equal(1);
1900
+ });
1901
+
1902
+ it('getLevelOrderNodes() accepts a native root node', function () {
1903
+ const levels = oc.getLevelOrderNodes($laolao);
1904
+
1905
+ levels.should.have.lengthOf(4);
1906
+ levels[0][0].should.equal($laolao);
1907
+ levels[1].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n2', 'n3', 'n4']);
1908
+ levels[2].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n5', 'n6', 'n7']);
1909
+ levels[3].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n8', 'n9', 'n10']);
1910
+ });
1911
+
1912
+ it('isInAction() accepts a native node and inspects native edge classes', function () {
1913
+ const nodeEl = $sumiao;
1914
+ const topEdgeEl = nodeEl.querySelector('.topEdge');
1915
+
1916
+ should.exist(topEdgeEl);
1917
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
1918
+ oc.isInAction(nodeEl).should.equal(true);
1919
+
1920
+ topEdgeEl.classList.remove(oc.options.icons.expandToUp);
1921
+ oc.isInAction(nodeEl).should.equal(false);
1922
+ });
1923
+
1924
+ it('switchHorizontalArrow() accepts a native node and toggles edge classes from native siblings', function () {
1925
+ const nodeEl = $sumiao;
1926
+ const leftEdgeEl = nodeEl.querySelector('.leftEdge');
1927
+ const rightEdgeEl = nodeEl.querySelector('.rightEdge');
1928
+
1929
+ oc.switchHorizontalArrow(nodeEl);
1930
+ leftEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(true);
1931
+ leftEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
1932
+ rightEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(true);
1933
+ rightEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
1934
+
1935
+ siblingElements(closestElement($sumiao, '.hierarchy')).forEach(function (siblingEl) {
1936
+ addClasses(siblingEl, 'hidden');
1937
+ });
1938
+ oc.switchHorizontalArrow(nodeEl);
1939
+
1940
+ leftEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
1941
+ leftEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(true);
1942
+ rightEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
1943
+ rightEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(true);
1944
+ });
1945
+
1946
+ it('switchVerticalArrow() accepts a native edge element and toggles vertical arrow classes', function () {
1947
+ const bottomEdgeEl = $sumiao.querySelector('.bottomEdge');
1948
+
1949
+ should.exist(bottomEdgeEl);
1950
+ bottomEdgeEl.classList.add(oc.options.icons.expandToUp);
1951
+
1952
+ oc.switchVerticalArrow(bottomEdgeEl);
1953
+ bottomEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
1954
+ bottomEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
1955
+
1956
+ oc.switchVerticalArrow(bottomEdgeEl);
1957
+ bottomEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(true);
1958
+ bottomEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(false);
1959
+ });
1960
+
1961
+ it('panStartHandler() accepts a native chart element and updates transform on mousemove', function () {
1962
+ let moveEvent;
1963
+
1964
+ oc.chart.style.transform = 'none';
1965
+
1966
+ oc.panStartHandler({
1967
+ chart: oc.chart,
1968
+ target: oc.chart,
1969
+ pageX: 10,
1970
+ pageY: 20
1971
+ });
1972
+
1973
+ getState(oc.chart, 'panning').should.equal(true);
1974
+ moveEvent = new window.Event('mousemove', { bubbles: true });
1975
+ Object.defineProperty(moveEvent, 'pageX', { value: 30 });
1976
+ Object.defineProperty(moveEvent, 'pageY', { value: 45 });
1977
+ oc.chart.dispatchEvent(moveEvent);
1978
+
1979
+ oc.chart.style.transform.should.equal('matrix(1, 0, 0, 1, 20, 25)');
1980
+ });
1981
+
1982
+ it('panStartHandler() accepts wrapped native event payloads', function () {
1983
+ oc.chart.style.transform = 'none';
1984
+
1985
+ oc.panStartHandler({
1986
+ chart: oc.chart,
1987
+ originalEvent: {
1988
+ target: oc.chart,
1989
+ pageX: 10,
1990
+ pageY: 20
1991
+ }
1992
+ });
1993
+
1994
+ getState(oc.chart, 'panning').should.equal(true);
1995
+ });
1996
+
1997
+ it('panEndHandler() accepts a native chart element', function () {
1998
+ setState(oc.chart, 'panning', true);
1999
+ oc.chart.style.cursor = 'move';
2000
+
2001
+ oc.panEndHandler({ chart: oc.chart });
2002
+
2003
+ getState(oc.chart, 'panning').should.equal(false);
2004
+ oc.chart.style.cursor.should.equal('default');
2005
+ });
2006
+
2007
+ it('panEndHandler() accepts wrapped native chart payloads', function () {
2008
+ setState(oc.chart, 'panning', true);
2009
+ oc.chart.style.cursor = 'move';
2010
+
2011
+ oc.panEndHandler({
2012
+ originalEvent: {
2013
+ currentTarget: oc.chart
2014
+ }
2015
+ });
2016
+
2017
+ getState(oc.chart, 'panning').should.equal(false);
2018
+ oc.chart.style.cursor.should.equal('default');
2019
+ });
2020
+
2021
+ it('panEndHandler() removes both mousemove and touchmove listeners', function () {
2022
+ const removedTypes = [];
2023
+ const originalRemoveEventListener = oc.chart.removeEventListener;
2024
+
2025
+ oc.chart.removeEventListener = function (type, listener, options) {
2026
+ removedTypes.push(type);
2027
+ return originalRemoveEventListener.call(this, type, listener, options);
2028
+ };
2029
+
2030
+ oc.panStartHandler({
2031
+ chart: oc.chart,
2032
+ target: oc.chart,
2033
+ pageX: 10,
2034
+ pageY: 20
2035
+ });
2036
+ oc.panEndHandler({ chart: oc.chart });
2037
+
2038
+ removedTypes.should.include('mousemove');
2039
+ removedTypes.should.include('touchmove');
2040
+
2041
+ oc.chart.removeEventListener = originalRemoveEventListener;
2042
+ });
2043
+
2044
+ it('bindPan() wires document pan-end events with the native chart element', function () {
2045
+ const panChart = new OrgChart({
2046
+ chartContainer: '#chart-container',
2047
+ data: ds
2048
+ });
2049
+ const panEndSpy = sinon.stub(panChart, 'panEndHandler');
2050
+
2051
+ panChart.bindPan();
2052
+ document.dispatchEvent(new window.MouseEvent('mouseup', { bubbles: true }));
2053
+
2054
+ panEndSpy.should.have.been.called;
2055
+ panEndSpy.lastCall.args[0].chart.should.equal(panChart.chart);
2056
+ });
2057
+
2058
+ it('unbindPan() removes the native document pan-end listeners', function () {
2059
+ const panChart = new OrgChart({
2060
+ chartContainer: '#chart-container',
2061
+ data: ds
2062
+ });
2063
+ const panEndSpy = sinon.stub(panChart, 'panEndHandler');
2064
+
2065
+ panChart.bindPan();
2066
+ panChart.unbindPan();
2067
+ document.dispatchEvent(new window.MouseEvent('mouseup', { bubbles: true }));
2068
+
2069
+ panEndSpy.should.not.have.been.called;
2070
+ });
2071
+
2072
+ it('setChartScale() accepts a native chart element', function () {
2073
+ oc.chart.style.transform = 'none';
2074
+ sinon.stub(oc.chart, 'getBoundingClientRect').returns({ left: 0, top: 0 });
2075
+ sinon.stub(oc.chartContainer, 'getBoundingClientRect').returns({
2076
+ left: 0,
2077
+ top: 0,
2078
+ right: 200,
2079
+ bottom: 100,
2080
+ width: 200,
2081
+ height: 100
2082
+ });
2083
+ oc.setChartScale(oc.chart, 1.2);
2084
+
2085
+ oc.chart.style.transform.should.equal('matrix(1.2, 0, 0, 1.2, -20, -10)');
2086
+ });
2087
+
2088
+ it('setChartScale() keeps the chart-local anchor fixed even when the chart is offset inside the container', function () {
2089
+ sinon.stub(oc.chart, 'getBoundingClientRect').returns({ left: 100, top: 200 });
2090
+ oc.chart.style.transform = 'matrix(1, 0, 0, 1, 0, 0)';
2091
+ oc.setChartScale(oc.chart, 1.2, { x: 160, y: 245 });
2092
+
2093
+ oc.chart.style.transform.should.equal('matrix(1.2, 0, 0, 1.2, -12, -9)');
2094
+ });
2095
+
2096
+ it('zoomStartHandler() stores pinch state on the native chart element', function () {
2097
+ const pinchDistStub = sinon.stub(oc, 'getPinchDist').returns(12);
2098
+ const pinchCenterStub = sinon.stub(oc, 'getPinchCenter').returns({ x: 20, y: 30 });
2099
+
2100
+ oc.zoomStartHandler({
2101
+ orgChart: oc,
2102
+ touches: [{ clientX: 0, clientY: 0 }, { clientX: 3, clientY: 4 }]
2103
+ });
2104
+
2105
+ getState(oc.chart, 'pinching').should.equal(true);
2106
+ getState(oc.chart, 'pinchDistStart').should.equal(12);
2107
+ getState(oc.chart, 'pinchCenterStart').should.deep.equal({ x: 20, y: 30 });
2108
+ getState(oc.chart, 'pinchCenterEnd').should.deep.equal({ x: 20, y: 30 });
2109
+
2110
+ pinchDistStub.restore();
2111
+ pinchCenterStub.restore();
2112
+ });
2113
+
2114
+ it('zoomStartHandler() accepts wrapped native touch payloads', function () {
2115
+ const pinchDistStub = sinon.stub(oc, 'getPinchDist').returns(12);
2116
+ const pinchCenterStub = sinon.stub(oc, 'getPinchCenter').returns({ x: 20, y: 30 });
2117
+
2118
+ oc.zoomStartHandler({
2119
+ orgChart: oc,
2120
+ originalEvent: {
2121
+ touches: [{ clientX: 0, clientY: 0 }, { clientX: 3, clientY: 4 }]
2122
+ }
2123
+ });
2124
+
2125
+ getState(oc.chart, 'pinching').should.equal(true);
2126
+ getState(oc.chart, 'pinchDistStart').should.equal(12);
2127
+ getState(oc.chart, 'pinchCenterStart').should.deep.equal({ x: 20, y: 30 });
2128
+
2129
+ pinchDistStub.restore();
2130
+ pinchCenterStub.restore();
2131
+ });
2132
+
2133
+ it('zoomingHandler() stores pinch distance on the native chart element', function () {
2134
+ const pinchDistStub = sinon.stub(oc, 'getPinchDist').returns(18);
2135
+ const pinchCenterStub = sinon.stub(oc, 'getPinchCenter').returns({ x: 24, y: 36 });
2136
+
2137
+ setState(oc.chart, 'pinching', true);
2138
+ oc.zoomingHandler({ orgChart: oc });
2139
+
2140
+ getState(oc.chart, 'pinchDistEnd').should.equal(18);
2141
+ getState(oc.chart, 'pinchCenterEnd').should.deep.equal({ x: 24, y: 36 });
2142
+
2143
+ pinchDistStub.restore();
2144
+ pinchCenterStub.restore();
2145
+ });
2146
+
2147
+ it('zoomingHandler() accepts wrapped native touch payloads', function () {
2148
+ const pinchDistStub = sinon.stub(oc, 'getPinchDist').returns(18);
2149
+ const pinchCenterStub = sinon.stub(oc, 'getPinchCenter').returns({ x: 24, y: 36 });
2150
+
2151
+ setState(oc.chart, 'pinching', true);
2152
+ oc.zoomingHandler({
2153
+ orgChart: oc,
2154
+ originalEvent: {
2155
+ touches: [{ clientX: 0, clientY: 0 }, { clientX: 3, clientY: 4 }]
2156
+ }
2157
+ });
2158
+
2159
+ getState(oc.chart, 'pinchDistEnd').should.equal(18);
2160
+ getState(oc.chart, 'pinchCenterEnd').should.deep.equal({ x: 24, y: 36 });
2161
+
2162
+ pinchDistStub.restore();
2163
+ pinchCenterStub.restore();
2164
+ });
2165
+
2166
+ it('getPinchDist() accepts a native touch payload', function () {
2167
+ const distance = oc.getPinchDist({
2168
+ touches: [{ clientX: 0, clientY: 0 }, { clientX: 3, clientY: 4 }]
2169
+ });
2170
+
2171
+ distance.should.equal(5);
2172
+ });
2173
+
2174
+ it('getPinchCenter() accepts a native touch payload', function () {
2175
+ const pinchCenter = oc.getPinchCenter({
2176
+ touches: [{ clientX: 10, clientY: 20 }, { clientX: 30, clientY: 40 }]
2177
+ });
2178
+
2179
+ pinchCenter.should.deep.equal({ x: 20, y: 30 });
2180
+ });
2181
+
2182
+ it('zoomWheelHandler() passes the mouse anchor to setChartScale()', function () {
2183
+ const spy = sinon.spy(oc, 'setChartScale');
2184
+ const preventDefaultSpy = sinon.spy();
2185
+ sinon.stub(oc.chartContainer, 'getBoundingClientRect').returns({
2186
+ left: 10,
2187
+ top: 20,
2188
+ right: 210,
2189
+ bottom: 120,
2190
+ width: 200,
2191
+ height: 100
2192
+ });
2193
+
2194
+ oc.zoomWheelHandler({
2195
+ orgChart: oc,
2196
+ originalEvent: {
2197
+ deltaY: -1,
2198
+ clientX: 60,
2199
+ clientY: 45,
2200
+ preventDefault: preventDefaultSpy
2201
+ }
2202
+ });
2203
+
2204
+ preventDefaultSpy.should.have.been.calledOnce;
2205
+ spy.should.have.been.calledWith(oc.chart, 1.2, { x: 60, y: 45 });
2206
+ });
2207
+
2208
+ it('zoomWheelHandler() ignores wheel input when the pointer is outside the chart container', function () {
2209
+ const spy = sinon.spy(oc, 'setChartScale');
2210
+ const preventDefaultSpy = sinon.spy();
2211
+ sinon.stub(oc.chartContainer, 'getBoundingClientRect').returns({
2212
+ left: 10,
2213
+ top: 20,
2214
+ right: 210,
2215
+ bottom: 120,
2216
+ width: 200,
2217
+ height: 100
2218
+ });
2219
+
2220
+ oc.zoomWheelHandler({
2221
+ orgChart: oc,
2222
+ originalEvent: {
2223
+ deltaY: -1,
2224
+ clientX: 5,
2225
+ clientY: 45,
2226
+ preventDefault: preventDefaultSpy
2227
+ }
2228
+ });
2229
+
2230
+ preventDefaultSpy.should.not.have.been.called;
2231
+ spy.should.not.have.been.called;
2232
+ });
2233
+
2234
+ it('bindZoom() wires wheel events with the native chart container', function () {
2235
+ const zoomChart = new OrgChart({
2236
+ chartContainer: '#chart-container',
2237
+ data: ds
2238
+ });
2239
+ const zoomWheelSpy = sinon.stub(zoomChart, 'zoomWheelHandler');
2240
+
2241
+ zoomChart.bindZoom();
2242
+ zoomChart.chartContainer.dispatchEvent(new window.Event('wheel', { bubbles: true }));
2243
+
2244
+ zoomWheelSpy.should.have.been.called;
2245
+ zoomWheelSpy.lastCall.args[0].orgChart.should.equal(zoomChart);
2246
+ });
2247
+
2248
+ it('unbindZoom() removes native zoom listeners', function () {
2249
+ const zoomChart = new OrgChart({
2250
+ chartContainer: '#chart-container',
2251
+ data: ds
2252
+ });
2253
+ const zoomWheelSpy = sinon.stub(zoomChart, 'zoomWheelHandler');
2254
+
2255
+ zoomChart.bindZoom();
2256
+ zoomChart.unbindZoom();
2257
+ zoomChart.chartContainer.dispatchEvent(new window.Event('wheel', { bubbles: true }));
2258
+
2259
+ zoomWheelSpy.should.not.have.been.called;
2260
+ });
2261
+
2262
+ it('zoomEndHandler() passes a native chart element to setChartScale()', function () {
2263
+ const spy = sinon.spy(oc, 'setChartScale');
2264
+
2265
+ setState(oc.chart, 'pinching', true);
2266
+ setState(oc.chart, 'pinchDistStart', 10);
2267
+ setState(oc.chart, 'pinchDistEnd', 20);
2268
+ setState(oc.chart, 'pinchCenterEnd', { x: 50, y: 60 });
2269
+ oc.zoomEndHandler({ orgChart: oc });
2270
+
2271
+ spy.should.have.been.calledWith(oc.chart, 1.2, { x: 50, y: 60 });
2272
+ });
2273
+
2274
+ it('createNode() passes a native node to bindDragDrop() when draggable is enabled', function () {
2275
+ const draggableChart = new OrgChart({
2276
+ chartContainer: '#chart-container',
2277
+ data: ds,
2278
+ draggable: true
2279
+ });
2280
+ const spy = sinon.spy(draggableChart, 'bindDragDrop');
2281
+
2282
+ draggableChart.createNode({ id: 'n11', name: 'Li Xin', relationship: '000', level: 0 });
2283
+
2284
+ spy.should.have.been.called;
2285
+ spy.firstCall.args[0].nodeType.should.equal(1);
2286
+ });
2287
+
2288
+ it('buildInferiorNodes() accepts native hierarchy and node elements for vertical children', function () {
2289
+ const verticalChart = new OrgChart({
2290
+ chartContainer: '#chart-container',
2291
+ data: ds,
2292
+ verticalLevel: 2,
2293
+ visibleLevel: 1
2294
+ });
2295
+ const hierarchyEl = document.createElement('li');
2296
+ let nodeEl;
2297
+ let verticalNodesEl;
2298
+
2299
+ hierarchyEl.className = 'hierarchy';
2300
+ nodeEl = verticalChart.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 1 });
2301
+ hierarchyEl.appendChild(nodeEl);
2302
+
2303
+ verticalChart.buildInferiorNodes(hierarchyEl, nodeEl, {
2304
+ collapsed: true,
2305
+ children: [{ id: 'n12', name: 'Child', relationship: '000' }]
2306
+ }, 1);
2307
+
2308
+ verticalNodesEl = Array.from(hierarchyEl.children).find(function (childEl) {
2309
+ return childEl.classList && childEl.classList.contains('nodes');
2310
+ });
2311
+
2312
+ should.exist(verticalNodesEl);
2313
+ verticalNodesEl.classList.contains('vertical').should.equal(true);
2314
+ verticalNodesEl.classList.contains('hidden').should.equal(true);
2315
+ verticalNodesEl.querySelector('#n12').should.not.equal(null);
2316
+ });
2317
+
2318
+ it('buildInferiorNodes() keeps compact descendants on the native node element', function () {
2319
+ const hierarchyEl = document.createElement('li');
2320
+ const nodeEl = oc.createNode({ id: 'n11', name: 'Parent', relationship: '001', level: 0 });
2321
+
2322
+ hierarchyEl.className = 'hierarchy';
2323
+ hierarchyEl.appendChild(nodeEl);
2324
+
2325
+ oc.buildInferiorNodes(hierarchyEl, nodeEl, {
2326
+ compact: true,
2327
+ children: [{ id: 'n12', name: 'Child', relationship: '000' }]
2328
+ }, 0);
2329
+
2330
+ nodeEl.classList.contains('compact').should.equal(true);
2331
+ should.equal(hierarchyEl.querySelector('.nodes'), null);
2332
+ nodeEl.querySelector('#n12').should.not.equal(null);
2333
+ });
2334
+
2335
+ it('buildHierarchy() accepts a native hierarchy element for family tree spouse layout', function () {
2336
+ const hierarchyEl = document.createElement('li');
2337
+ let wrapperEl;
2338
+
2339
+ hierarchyEl.className = 'hierarchy';
2340
+
2341
+ oc.buildHierarchy(hierarchyEl, [[
2342
+ { id: 'n11', name: 'Spouse A', relationship: '001', outsider: false },
2343
+ {
2344
+ id: 'n12',
2345
+ name: 'Spouse B',
2346
+ relationship: '001',
2347
+ outsider: true,
2348
+ children: [[{ id: 'n13', name: 'Child', relationship: '000' }]]
2349
+ }
2350
+ ]]);
2351
+
2352
+ wrapperEl = hierarchyEl.firstElementChild;
2353
+
2354
+ should.exist(wrapperEl);
2355
+ wrapperEl.classList.contains('couple').should.equal(true);
2356
+ Array.from(wrapperEl.children).filter(function (childEl) {
2357
+ return childEl.classList && childEl.classList.contains('node');
2358
+ }).length.should.equal(2);
2359
+ wrapperEl.querySelector('#n12').should.not.equal(null);
2360
+ wrapperEl.querySelector('.nodes').should.not.equal(null);
2361
+ });
2362
+
2363
+ it('addAncestors() wraps the original root content into a different hierarchy from the new ancestor', function () {
2364
+ let ancestorNodeEl;
2365
+ let originalRootNodeEl;
2366
+
2367
+ oc.addAncestors({ id: 'n0', name: 'Lao Ye', relationship: '001' }, 'n0');
2368
+
2369
+ ancestorNodeEl = document.getElementById('n0');
2370
+ originalRootNodeEl = document.getElementById('n1');
2371
+
2372
+ should.exist(ancestorNodeEl);
2373
+ should.exist(originalRootNodeEl);
2374
+ originalRootNodeEl.closest('.hierarchy').should.not.equal(ancestorNodeEl.closest('.hierarchy'));
2375
+ });
2376
+
2377
+ it('addDescendants() accepts a native parent element and appends descendant hierarchy nodes', function () {
2378
+ oc.addDescendants([{ id: 'n11', name: 'Li Xin' }], $bomiao);
2379
+
2380
+ should.exist($bomiao.nextElementSibling);
2381
+ matchesSelector($bomiao.nextElementSibling, '.nodes').should.be.true;
2382
+ childElements($bomiao.nextElementSibling, '.hierarchy').should.have.lengthOf(1);
2383
+ queryAll('#n11', $bomiao.nextElementSibling).should.have.lengthOf(1);
2384
+ });
2385
+
2386
+ it('showDropZones() accepts a native dragged node', function () {
2387
+ oc.showDropZones($heihei);
2388
+
2389
+ getState(oc.chart, 'dragged').should.equal($heihei);
2390
+ queryAll('.allowedDrop', oc.chart).length.should.equal(queryAll('.node', oc.chart).length);
2391
+ });
2392
+
2393
+ it('hideDropZones() clears all allowedDrop classes', function () {
2394
+ oc.showDropZones($heihei);
2395
+
2396
+ oc.hideDropZones();
2397
+
2398
+ queryAll('.allowedDrop', oc.chart).should.have.lengthOf(0);
2399
+ });
2400
+
2401
+ it('processExternalDrop() accepts native drop and dragged nodes', function () {
2402
+ const dropSpy = sinon.spy();
2403
+
2404
+ $bomiao.addEventListener('drop', dropSpy);
2405
+
2406
+ oc.processExternalDrop($bomiao, $heihei);
2407
+
2408
+ getState(oc.chart, 'dragged').should.equal($heihei);
2409
+ dropSpy.should.have.been.calledOnce;
2410
+ dropSpy.firstCall.args[0].type.should.equal('drop');
2411
+ });
2412
+
2413
+ it('exportPNG() creates and reuses a native download anchor', function () {
2414
+ const originalNavigator = window.navigator;
2415
+ const originalClick = window.HTMLAnchorElement.prototype.click;
2416
+ const originalWebkitAppearance = document.documentElement.style.WebkitAppearance;
2417
+ const clickSpy = sinon.spy();
2418
+ const fakeCanvas = {
2419
+ toDataURL: function () {
2420
+ return 'data:image/png;base64,fake';
2421
+ }
2422
+ };
2423
+
2424
+ Object.defineProperty(window, 'navigator', {
2425
+ configurable: true,
2426
+ value: {
2427
+ appName: 'Netscape',
2428
+ appVersion: 'Chrome'
2429
+ }
2430
+ });
2431
+ document.documentElement.style.WebkitAppearance = 'none';
2432
+ window.HTMLAnchorElement.prototype.click = clickSpy;
2433
+
2434
+ oc.exportPNG(fakeCanvas, 'UnitExportPng');
2435
+ oc.exportPNG(fakeCanvas, 'UnitExportPng2');
2436
+
2437
+ queryAll('.download-btn', $container).should.have.lengthOf(1);
2438
+ query('.download-btn', $container).getAttribute('download').should.equal('UnitExportPng2.png');
2439
+ query('.download-btn', $container).getAttribute('href').should.equal('data:image/png;base64,fake');
2440
+ clickSpy.callCount.should.equal(2);
2441
+
2442
+ Object.defineProperty(window, 'navigator', {
2443
+ configurable: true,
2444
+ value: originalNavigator
2445
+ });
2446
+ document.documentElement.style.WebkitAppearance = originalWebkitAppearance;
2447
+ window.HTMLAnchorElement.prototype.click = originalClick;
2448
+ });
2449
+
2450
+ it('export() passes the visible chart element to html2canvas and exports png by default', function (done) {
2451
+ const originalHtml2canvas = global.html2canvas;
2452
+ const originalWindowHtml2canvas = window.html2canvas;
2453
+ const exportPNGStub = sinon.stub(oc, 'exportPNG');
2454
+ const fakeCanvas = {
2455
+ toDataURL: function () {
2456
+ return 'data:image/png;base64,fake';
2457
+ }
2458
+ };
2459
+
2460
+ global.html2canvas = function (element, options) {
2461
+ try {
2462
+ element.should.equal(oc.chart);
2463
+ options.width.should.equal(oc.chart.clientWidth);
2464
+ options.height.should.equal(oc.chart.clientHeight);
2465
+ options.scale.should.equal(2);
2466
+ options.useCORS.should.equal(true);
2467
+ } catch (error) {
2468
+ done(error);
2469
+ return {
2470
+ then: function () {}
2471
+ };
2472
+ }
2473
+
2474
+ return {
2475
+ then: function (resolve) {
2476
+ resolve(fakeCanvas);
2477
+ return {
2478
+ then: function () {}
2479
+ };
2480
+ }
2481
+ };
2482
+ };
2483
+
2484
+ window.html2canvas = global.html2canvas;
2485
+
2486
+ oc.export('UnitExport');
2487
+
2488
+ setTimeout(function () {
2489
+ try {
2490
+ exportPNGStub.should.have.been.calledOnce;
2491
+ exportPNGStub.firstCall.args[0].should.equal(fakeCanvas);
2492
+ exportPNGStub.firstCall.args[1].should.equal('UnitExport');
2493
+ $container.classList.contains('canvasContainer').should.be.false;
2494
+ query('.mask', $container).classList.contains('hidden').should.be.true;
2495
+ global.html2canvas = originalHtml2canvas;
2496
+ window.html2canvas = originalWindowHtml2canvas;
2497
+ exportPNGStub.restore();
2498
+ done();
2499
+ } catch (error) {
2500
+ global.html2canvas = originalHtml2canvas;
2501
+ window.html2canvas = originalWindowHtml2canvas;
2502
+ exportPNGStub.restore();
2503
+ done(error);
2504
+ }
2505
+ }, 0);
2506
+ });
2507
+
2508
+ it('export() returns false when the chart container already has a spinner', function () {
2509
+ const html2canvasStub = sinon.stub(global, 'html2canvas');
2510
+ const spinnerEl = document.createElement('i');
2511
+
2512
+ spinnerEl.className = 'spinner';
2513
+ $container.appendChild(spinnerEl);
2514
+
2515
+ oc.export('BlockedExport').should.equal(false);
2516
+ html2canvasStub.should.not.have.been.called;
2517
+
2518
+ spinnerEl.remove();
2519
+ html2canvasStub.restore();
2520
+ });
2521
+
2522
+ it('export() routes pdf exports to exportPDF()', function (done) {
2523
+ const originalHtml2canvas = global.html2canvas;
2524
+ const originalWindowHtml2canvas = window.html2canvas;
2525
+ const exportPDFStub = sinon.stub(oc, 'exportPDF');
2526
+ const fakeCanvas = {
2527
+ toDataURL: function () {
2528
+ return 'data:image/png;base64,fake';
2529
+ }
2530
+ };
2531
+
2532
+ global.html2canvas = function () {
2533
+ return {
2534
+ then: function (resolve) {
2535
+ resolve(fakeCanvas);
2536
+ return {
2537
+ then: function () {}
2538
+ };
2539
+ }
2540
+ };
2541
+ };
2542
+
2543
+ window.html2canvas = global.html2canvas;
2544
+
2545
+ oc.export('UnitExportPdf', 'pdf');
2546
+
2547
+ setTimeout(function () {
2548
+ try {
2549
+ exportPDFStub.should.have.been.calledOnce;
2550
+ exportPDFStub.firstCall.args[0].should.equal(fakeCanvas);
2551
+ exportPDFStub.firstCall.args[1].should.equal('UnitExportPdf');
2552
+ global.html2canvas = originalHtml2canvas;
2553
+ window.html2canvas = originalWindowHtml2canvas;
2554
+ exportPDFStub.restore();
2555
+ done();
2556
+ } catch (error) {
2557
+ global.html2canvas = originalHtml2canvas;
2558
+ window.html2canvas = originalWindowHtml2canvas;
2559
+ exportPDFStub.restore();
2560
+ done(error);
2561
+ }
2562
+ }, 0);
2563
+ });
2564
+ });