orgchart 5.0.0 → 7.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 (86) hide show
  1. package/.github/workflows/publish.yml +35 -0
  2. package/README.md +214 -123
  3. package/README.zh-cn.md +232 -100
  4. package/README.zh-tw.md +232 -100
  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 +144 -94
  20. package/demo/edit-hybrid-chart.html +177 -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 +14 -11
  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 +10 -3
  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 +166 -112
  39. package/demo/option-createNode.html +28 -17
  40. package/demo/options-compact-drag.html +82 -0
  41. package/demo/options-export-navigation.html +76 -0
  42. package/demo/options-horizontal-template.html +80 -0
  43. package/demo/options-hybrid-navigation.html +71 -0
  44. package/demo/pan-zoom.html +20 -11
  45. package/demo/reload-data.html +17 -14
  46. package/demo/report-path.html +59 -29
  47. package/demo/responsive-design.html +14 -11
  48. package/demo/right2left.html +12 -9
  49. package/demo/toggle-sibs-resp.html +12 -9
  50. package/demo/ul-datasource.html +11 -8
  51. package/demo/vertical-level.html +12 -9
  52. package/dist/css/{jquery.orgchart.css → orgchart.css} +154 -80
  53. package/dist/css/orgchart.min.css +1 -0
  54. package/dist/js/orgchart.js +4474 -0
  55. package/dist/js/orgchart.min.js +2 -0
  56. package/dist/js/orgchart.min.js.map +1 -0
  57. package/gulpfile.js +42 -27
  58. package/package.json +33 -32
  59. package/src/css/{jquery.orgchart.css → orgchart.css} +154 -80
  60. package/src/js/orgchart.js +4474 -0
  61. package/test/compatibility/README.md +123 -0
  62. package/test/compatibility/browser.tests.js +1279 -0
  63. package/test/compatibility/options.tests.js +628 -0
  64. package/test/cypress/e2e/demo-interactions.cy.js +91 -0
  65. package/test/cypress/e2e/demo-rendering.cy.js +161 -0
  66. package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +23 -0
  67. package/test/cypress/e2e/edit-chart.cy.js +20 -0
  68. package/test/cypress/e2e/level-offset.cy.js +21 -0
  69. package/test/cypress/e2e/local-datasource.cy.js +16 -16
  70. package/test/cypress/e2e/minimap.cy.js +66 -0
  71. package/test/cypress/e2e/ondemand-loading-data.cy.js +34 -10
  72. package/test/cypress/e2e/pan-zoom.cy.js +165 -0
  73. package/test/cypress/e2e/report-path.cy.js +20 -0
  74. package/test/cypress/e2e/toggle-sibs-resp.cy.js +4 -4
  75. package/test/cypress/e2e/vertical-vevel.cy.js +7 -5
  76. package/test/e2e/local-datasource/test.js +1 -1
  77. package/test/integration/init-tests.js +192 -99
  78. package/test/integration/tests.js +1043 -57
  79. package/test/unit/lifecycle-builders-export.tests.js +1487 -0
  80. package/test/unit/minimap.tests.js +210 -0
  81. package/test/unit/tests.js +2372 -253
  82. package/dist/css/jquery.orgchart.min.css +0 -1
  83. package/dist/js/jquery.orgchart.js +0 -1741
  84. package/dist/js/jquery.orgchart.min.js +0 -5
  85. package/dist/js/jquery.orgchart.min.js.map +0 -1
  86. package/src/js/jquery.orgchart.js +0 -1741
@@ -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,78 @@ 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({
82
- 'data': ds,
236
+ document.body.innerHTML = '<div id="chart-container"></div>';
237
+ $container = document.getElementById('chart-container');
238
+ oc = new OrgChart({
239
+ chartContainer: '#chart-container',
240
+ 'data': JSON.parse(JSON.stringify(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
+ children: [
280
+ {
281
+ id: 'child',
282
+ name: 'Child'
283
+ }
284
+ ]
285
+ });
104
286
  });
105
287
 
106
288
  it('getHierarchy()', function () {
107
289
  oc.getHierarchy().should.deep.equal(hierarchy);
108
290
 
109
- var oc2 = $('#chart-container').orgchart({
291
+ const oc2 = new OrgChart({
292
+ chartContainer: '#chart-container',
110
293
  'data': { name: 'Lao Lao',
111
294
  'children': [
112
295
  { name: 'Bo Miao' }
@@ -114,249 +297,621 @@ describe('orgchart -- unit tests', function () {
114
297
  }
115
298
  });
116
299
  oc2.getHierarchy().should.include('Error');
117
- oc2.$chart.empty();
300
+ oc2.chart.innerHTML = "";
118
301
  oc2.getHierarchy().should.include('Error');
119
- oc2.$chart = undefined;
302
+ oc2.chart = null;
120
303
  oc2.getHierarchy().should.include('Error');
121
304
  });
122
305
 
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");
306
+ it('triggerShowEvent() accepts a native node', function () {
307
+ const eventSpy = sinon.spy();
171
308
 
172
- done();
173
- } catch(err) {
174
- done(err);
175
- }
309
+ $sumiao.addEventListener('show-children.orgchart', eventSpy);
310
+
311
+ oc.triggerShowEvent($sumiao, 'children');
312
+
313
+ eventSpy.should.have.been.calledOnce;
314
+ eventSpy.firstCall.args[0].type.should.equal('show-children.orgchart');
315
+ });
316
+
317
+ it('triggerHideEvent() accepts a native node', function () {
318
+ const eventSpy = sinon.spy();
319
+
320
+ $sumiao.addEventListener('hide-children.orgchart', eventSpy);
321
+
322
+ oc.triggerHideEvent($sumiao, 'children');
323
+
324
+ eventSpy.should.have.been.calledOnce;
325
+ eventSpy.firstCall.args[0].type.should.equal('hide-children.orgchart');
326
+ });
327
+
328
+ it('getNodeState()', function () {
329
+ const stateDs = JSON.parse(baseDs);
330
+ let chartEl;
331
+ let laolaoEl;
332
+ let bomiaoEl;
333
+ let sumiaoEl;
334
+ let tiehuaEl;
335
+ let heiheiEl;
336
+ let pangpangEl;
337
+ let dandanEl;
338
+
339
+ $container.innerHTML = '';
340
+ oc = new OrgChart({
341
+ chartContainer: '#chart-container',
342
+ data: stateDs,
343
+ nodeContent: 'title',
344
+ visibleLevel: 2,
345
+ verticalLevel: 3
346
+ });
347
+ chartEl = oc.chart;
348
+ laolaoEl = query('#n1', chartEl);
349
+ bomiaoEl = query('#n2', chartEl);
350
+ sumiaoEl = query('#n3', chartEl);
351
+ tiehuaEl = query('#n5', chartEl);
352
+ heiheiEl = query('#n6', chartEl);
353
+ pangpangEl = query('#n7', chartEl);
354
+ dandanEl = query('#n8', chartEl);
355
+
356
+ oc.getNodeState(laolaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->self');
357
+ oc.getNodeState(laolaoEl, 'parent').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->parent');
358
+ oc.getNodeState(laolaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': true }, 'laolao->children');
359
+ oc.getNodeState(laolaoEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'laolao->siblings');
360
+
361
+ oc.getNodeState(bomiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->self');
362
+ oc.getNodeState(bomiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->parent');
363
+ oc.getNodeState(bomiaoEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'bomiao->children');
364
+ oc.getNodeState(bomiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'bomiao->siblings');
365
+
366
+ oc.getNodeState(sumiaoEl).should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->self');
367
+ oc.getNodeState(sumiaoEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->parent');
368
+ oc.getNodeState(sumiaoEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'sumiao->children');
369
+ oc.getNodeState(sumiaoEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': true }, 'sumiao->siblings');
370
+
371
+ oc.getNodeState(tiehuaEl).should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->self');
372
+ oc.getNodeState(tiehuaEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'tiehua->parent');
373
+ oc.getNodeState(tiehuaEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->children');
374
+ oc.getNodeState(tiehuaEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'tiehua->siblings');
375
+
376
+ oc.getNodeState(heiheiEl).should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->self');
377
+ oc.getNodeState(heiheiEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'heihei->parent');
378
+ oc.getNodeState(heiheiEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->children');
379
+ oc.getNodeState(heiheiEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'heihei->siblings');
380
+
381
+ oc.getNodeState(pangpangEl).should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->self');
382
+ oc.getNodeState(pangpangEl, 'parent').should.deep.equal({ 'exist': true, 'visible': true }, 'pangpang->parent');
383
+ oc.getNodeState(pangpangEl, 'children').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->children');
384
+ oc.getNodeState(pangpangEl, 'siblings').should.deep.equal({ 'exist': true, 'visible': false }, 'pangpang->siblings');
385
+
386
+ oc.getNodeState(dandanEl).should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->self');
387
+ oc.getNodeState(dandanEl, 'parent').should.deep.equal({ 'exist': true, 'visible': false }, 'dandan->parent');
388
+ oc.getNodeState(dandanEl, 'children').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->children');
389
+ oc.getNodeState(dandanEl, 'siblings').should.deep.equal({ 'exist': false, 'visible': false }, 'dandan->siblings');
390
+ });
391
+
392
+ it('triggerInitEvent() calls initCompleted with the native chart element', function () {
393
+ const originalMutationObserver = global.MutationObserver;
394
+ const originalWindowMutationObserver = window.MutationObserver;
395
+ const observeSpy = sinon.spy();
396
+ const disconnectSpy = sinon.spy();
397
+ const initCompletedSpy = sinon.spy();
398
+
399
+ const fakeMutationObserver = function (callback) {
400
+ this.observe = function (target, options) {
401
+ observeSpy(target, options);
402
+ callback([
403
+ {
404
+ addedNodes: [oc.chart]
405
+ }
406
+ ]);
407
+ };
408
+ this.disconnect = disconnectSpy;
176
409
  };
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
- }
410
+
411
+ global.MutationObserver = fakeMutationObserver;
412
+ window.MutationObserver = fakeMutationObserver;
413
+
414
+ oc.options.initCompleted = initCompletedSpy;
415
+ oc.triggerInitEvent();
416
+
417
+ observeSpy.should.have.been.calledOnce;
418
+ observeSpy.firstCall.args[0].should.equal($container);
419
+ observeSpy.firstCall.args[1].should.deep.equal({ childList: true });
420
+ disconnectSpy.should.have.been.calledOnce;
421
+ initCompletedSpy.should.have.been.calledOnce;
422
+ initCompletedSpy.firstCall.args[0].should.equal(oc.chart);
423
+
424
+ global.MutationObserver = originalMutationObserver;
425
+ window.MutationObserver = originalWindowMutationObserver;
426
+ });
427
+
428
+ it('triggerInitEvent() triggers init.orgchart on the chart collection', function () {
429
+ const originalMutationObserver = global.MutationObserver;
430
+ const originalWindowMutationObserver = window.MutationObserver;
431
+ const eventSpy = sinon.spy();
432
+
433
+ const fakeMutationObserver = function (callback) {
434
+ this.observe = function () {
435
+ callback([
436
+ {
437
+ addedNodes: [oc.chart]
438
+ }
439
+ ]);
440
+ };
441
+ this.disconnect = function () {};
442
+ };
443
+
444
+ global.MutationObserver = fakeMutationObserver;
445
+ window.MutationObserver = fakeMutationObserver;
446
+ oc.chart.addEventListener('init.orgchart', eventSpy);
447
+
448
+ oc.triggerInitEvent();
449
+
450
+ eventSpy.should.have.been.calledOnce;
451
+ eventSpy.firstCall.args[0].type.should.equal('init.orgchart');
452
+
453
+ global.MutationObserver = originalMutationObserver;
454
+ window.MutationObserver = originalWindowMutationObserver;
455
+ });
456
+
457
+ it('handleCompactNodes() tags compact nodes by compact-ancestor parity', function () {
458
+ const compactRootEl = document.createElement('div');
459
+ const compactChildEl = document.createElement('div');
460
+ const compactGrandchildEl = document.createElement('div');
461
+
462
+ compactRootEl.className = 'node compact';
463
+ compactChildEl.className = 'node compact';
464
+ compactGrandchildEl.className = 'node compact';
465
+ compactRootEl.appendChild(compactChildEl);
466
+ compactChildEl.appendChild(compactGrandchildEl);
467
+ oc.chart.appendChild(compactRootEl);
468
+
469
+ oc.handleCompactNodes();
470
+
471
+ compactRootEl.classList.contains('even').should.equal(true);
472
+ compactChildEl.classList.contains('odd').should.equal(true);
473
+ compactGrandchildEl.classList.contains('even').should.equal(true);
183
474
  });
184
475
 
185
476
  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($());
477
+ oc.getRelatedNodes().should.deep.equal([]);
478
+ oc.getRelatedNodes({}, 'children').should.deep.equal([]);
479
+ oc.getRelatedNodes(query('.hierarchy', oc.chart), 'children').should.deep.equal([]);
480
+ oc.getRelatedNodes(query('.node', oc.chart), 'child').should.deep.equal([]);
190
481
 
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($());
482
+ should.equal(oc.getRelatedNodes($laolao, 'parent'), null);
483
+ oc.getRelatedNodes($laolao, 'children').should.have.members([$bomiao, $sumiao, $hongmiao]);
484
+ oc.getRelatedNodes($laolao, 'siblings').should.deep.equal([]);
194
485
 
195
- oc.getRelatedNodes($bomiao, 'parent').should.deep.equal($laolao);
486
+ oc.getRelatedNodes($bomiao, 'parent').should.equal($laolao);
196
487
  oc.getRelatedNodes($bomiao, 'children').should.have.lengthOf(0);
197
- oc.getRelatedNodes($bomiao, 'siblings').toArray().should.members([$sumiao[0], $hongmiao[0]]);
488
+ oc.getRelatedNodes($bomiao, 'siblings').should.have.members([$sumiao, $hongmiao]);
198
489
  });
199
490
 
200
491
  it('getParent()', function () {
201
- oc.getParent().should.deep.equal($());
202
- oc.getParent({}).should.deep.equal($());
203
- oc.getParent($('.hierarchy:first')).should.deep.equal($());
492
+ should.equal(oc.getParent(), null);
493
+ should.equal(oc.getParent({}), null);
494
+ should.equal(oc.getParent(query('.hierarchy', oc.chart)), null);
204
495
 
205
- oc.getParent($laolao).should.deep.equal($());
206
- oc.getParent($bomiao).should.deep.equal($laolao);
207
- oc.getParent($sandan).should.deep.equal($pangpang);
496
+ should.equal(oc.getParent($laolao), null);
497
+ oc.getParent($bomiao).should.equal($laolao);
498
+ oc.getParent($sandan).should.equal($pangpang);
208
499
  });
209
500
 
210
501
  it('getSiblings()', function () {
211
- oc.getSiblings().should.deep.equal($());
212
- oc.getSiblings({}).should.deep.equal($());
213
- oc.getSiblings($('.hierarchy:first')).should.deep.equal($());
502
+ oc.getSiblings().should.deep.equal([]);
503
+ oc.getSiblings({}).should.deep.equal([]);
504
+ oc.getSiblings(query('.hierarchy', oc.chart)).should.deep.equal([]);
214
505
 
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($());
506
+ oc.getSiblings($laolao).should.deep.equal([]);
507
+ oc.getSiblings($bomiao).should.have.members([$sumiao, $hongmiao]);
508
+ oc.getSiblings($sandan).should.deep.equal([]);
218
509
  });
219
510
 
220
511
  it('getChildren()', function () {
221
- oc.getChildren().should.deep.equal($());
222
- oc.getChildren({}).should.deep.equal($());
223
- oc.getChildren($('.hierarchy:first')).should.deep.equal($());
512
+ oc.getChildren().should.deep.equal([]);
513
+ oc.getChildren({}).should.deep.equal([]);
514
+ oc.getChildren(query('.hierarchy', oc.chart)).should.deep.equal([]);
224
515
 
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($());
516
+ oc.getChildren($laolao).should.have.members([$bomiao, $sumiao, $hongmiao]);
517
+ oc.getChildren($bomiao).should.deep.equal([]);
518
+ oc.getChildren($sumiao).should.have.members([$tiehua, $heihei, $pangpang]);
519
+ oc.getChildren($sandan).should.deep.equal([]);
229
520
  });
230
521
 
231
522
  it('hideParent()', function () {
232
- var spy = sinon.spy(oc, 'hideParent');
233
- var spy2 = sinon.spy(oc, 'hideSiblings');
523
+ const spy = sinon.spy(oc, 'hideParent');
524
+ const spy2 = sinon.spy(oc, 'hideSiblings');
525
+
234
526
  oc.hideParent($heihei);
527
+ setOneTimeListener($sumiao, 'transitionend', null, function (event) {
528
+ event.parent = parentEl;
529
+ spy.call(oc, event);
530
+ });
235
531
  spy.should.have.been.callCount(2);
236
- spy.getCall(0).should.have.been.calledWithMatch($heihei);
237
- spy.getCall(1).should.have.been.calledWithMatch($sumiao);
532
+ spy.getCall(0).args[0].should.equal($heihei);
533
+ spy.getCall(1).args[0].should.equal($sumiao);
238
534
  spy2.should.have.been.callCount(2);
239
- spy2.getCall(0).should.have.been.calledWithMatch($heihei);
240
- spy2.getCall(1).should.have.been.calledWithMatch($sumiao);
535
+ spy2.getCall(0).args[0].should.equal($heihei);
536
+ spy2.getCall(1).args[0].should.equal($sumiao);
537
+ });
538
+
539
+ it('hideParent() hides the transitioned parent node', function () {
540
+ oc.hideParent($sumiao);
541
+
542
+ triggerEvent($laolao, 'transitionend');
543
+
544
+ $laolao.classList.contains('sliding').should.equal(false);
545
+ $laolao.classList.contains('hidden').should.equal(true);
546
+ });
547
+
548
+ it('hideParent() clears inAjax when the parent node contains a spinner', function () {
549
+ const spinnerEl = document.createElement('i');
550
+
551
+ spinnerEl.className = 'spinner';
552
+ $sumiao.appendChild(spinnerEl);
553
+ setState(oc.chart, 'inAjax', true);
554
+
555
+ oc.hideParent($heihei);
556
+
557
+ getState(oc.chart, 'inAjax').should.be.false;
558
+ spinnerEl.remove();
241
559
  });
242
560
 
243
561
  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;
562
+ const parentEl = firstSibling(closestElement($heihei, '.nodes'), '.node');
563
+
564
+ addClasses($sumiao, 'sliding slide-down');
565
+ oc.hideParentEnd({ animatedNode: $sumiao, parent: parentEl });
566
+ matchesSelector($sumiao, 'sliding').should.be.false;
567
+ matchesSelector(parentEl, '.hidden').should.be.true;
568
+ });
569
+
570
+ it('hideParentEnd() accepts native animated and parent payloads', function () {
571
+ addClasses($sumiao, 'sliding');
572
+ removeClasses($sumiao, 'hidden');
573
+
574
+ oc.hideParentEnd({
575
+ animatedNode: $sumiao,
576
+ parent: $sumiao
577
+ });
578
+
579
+ $sumiao.classList.contains('sliding').should.equal(false);
580
+ $sumiao.classList.contains('hidden').should.equal(true);
581
+ });
582
+
583
+ it('hideParentEnd() accepts wrapped native animated and parent payloads', function () {
584
+ addClasses($sumiao, 'sliding');
585
+
586
+ oc.hideParentEnd({
587
+ originalEvent: {
588
+ target: $sumiao,
589
+ parent: $heihei
590
+ }
591
+ });
592
+
593
+ $sumiao.classList.contains('sliding').should.equal(false);
594
+ $heihei.classList.contains('hidden').should.equal(true);
251
595
  });
252
596
 
253
597
  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');
598
+ const spy = sinon.spy(oc, 'repaint');
599
+
600
+ addClasses(closestElement($heihei, '.hierarchy'), 'isAncestorsCollapsed');
601
+ addClasses($sumiao, 'slide-down hidden');
602
+ addClasses(closestElement($sumiao, '.hierarchy'), 'isAncestorsCollapsed');
603
+ addClasses($laolao, 'slide-down hidden');
258
604
  oc.showParent($heihei);
259
605
  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;
606
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isAncestorsCollapsed').should.be.false;
607
+ matchesSelector($sumiao, '.slide-down, .hidden').should.be.false;
608
+ matchesSelector(closestElement($sumiao, '.hierarchy'), '.isAncestorsCollapsed').should.be.true;
609
+ matchesSelector($laolao, '.slide-down.hidden').should.be.true;
610
+ matchesSelector($sumiao, '.sliding').should.be.true;
611
+ matchesSelector($sumiao, '.slide-down').should.be.false;
266
612
  });
267
613
 
268
614
  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');
615
+ const spy = sinon.spy(oc, 'showParentEnd');
616
+ const spy2 = sinon.spy(oc, 'isInAction');
617
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
618
+
619
+ addClasses($sumiao, 'sliding');
620
+ removeClasses($sumiao, 'slide-down');
621
+ setOneTimeListener($sumiao, 'transitionend', null, function (event) {
622
+ event.node = $heihei;
623
+ spy.call(oc, event);
624
+ });
625
+ triggerEvent($sumiao, 'transitionend');
274
626
  spy.should.have.been.called;
275
- $sumiao.is('.sliding').should.be.false;
627
+ matchesSelector($sumiao, '.sliding').should.be.false;
276
628
  spy2.should.have.been.calledWith($heihei);
277
629
  spy3.should.not.have.been.called;
278
630
  });
279
631
 
632
+ it('showParentEnd() accepts native animated and node payloads', function () {
633
+ addClasses($sumiao, 'sliding');
634
+
635
+ oc.showParentEnd({
636
+ animatedNode: $sumiao,
637
+ node: $heihei
638
+ });
639
+
640
+ $sumiao.classList.contains('sliding').should.equal(false);
641
+ });
642
+
643
+ it('showParentEnd() accepts wrapped native animated and node payloads', function () {
644
+ addClasses($sumiao, 'sliding');
645
+
646
+ oc.showParentEnd({
647
+ originalEvent: {
648
+ target: $sumiao,
649
+ node: $heihei
650
+ }
651
+ });
652
+
653
+ $sumiao.classList.contains('sliding').should.equal(false);
654
+ });
655
+
280
656
  it('hideChildren()', function () {
281
- var spy = sinon.spy(oc, 'repaint');
657
+ const spy = sinon.spy(oc, 'repaint');
658
+
282
659
  oc.hideChildren($sumiao);
660
+
283
661
  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;
662
+ matchesSelector(closestElement($sumiao, '.hierarchy'), '.isChildrenCollapsed').should.be.true;
663
+ childElements(firstSibling($sumiao, '.nodes'), '.isCollapsedDescendant').should.lengthOf(3);
664
+ matchesSelector($tiehua, '.sliding.slide-up').should.be.true;
665
+ matchesSelector($heihei, '.sliding.slide-up').should.be.true;
666
+ matchesSelector($pangpang, '.sliding.slide-up').should.be.true;
667
+ matchesSelector($erdan, '.sliding.slide-up').should.be.true;
290
668
  });
291
669
 
292
670
  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;
671
+ const spy2 = sinon.spy(oc, 'isInAction');
672
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
673
+
674
+ addClasses($tiehua, 'sliding slide-up');
675
+ oc.hideChildrenEnd({ animatedNodes: $tiehua, node: $sumiao });
676
+ matchesSelector($tiehua, '.sliding').should.be.false;
677
+ matchesSelector(closestElement($tiehua, '.nodes'), '.hidden').should.be.true;
301
678
  spy2.should.have.been.calledWith($sumiao);
302
679
  spy3.should.not.have.been.called;
303
680
  });
304
681
 
682
+ it('hideChildrenEnd() accepts direct animated and node payloads', function () {
683
+ $sumiao.dispatchEvent(new window.MouseEvent('mouseenter'));
684
+ const bottomEdgeEl = query('.bottomEdge', $sumiao);
685
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
686
+
687
+ removeClasses($tiehua, 'hidden');
688
+ addClasses($tiehua, 'sliding');
689
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
690
+ bottomEdgeEl.classList.add(oc.options.icons.collapseToDown);
691
+
692
+ oc.hideChildrenEnd({ animatedNodes: [$tiehua], node: $sumiao });
693
+
694
+ $tiehua.classList.contains('sliding').should.equal(false);
695
+ closestElement($tiehua, '.nodes').classList.contains('hidden').should.equal(true);
696
+ verticalSpy.should.have.been.calledWith(bottomEdgeEl);
697
+
698
+ verticalSpy.restore();
699
+ });
700
+
701
+ it('hideChildrenEnd() accepts wrapped native animated and node payloads', function () {
702
+ $sumiao.dispatchEvent(new window.MouseEvent('mouseenter'));
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
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
781
+ const topEdgeEl = query('.topEdge', $heihei);
782
+ const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
783
+
784
+ addClasses($tiehua, 'sliding');
785
+ addClasses($pangpang, 'sliding');
786
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
787
+
788
+ oc.showSiblingsEnd({
789
+ node: $heihei,
790
+ visibleNodes: [$tiehua, $pangpang]
791
+ });
792
+
793
+ $tiehua.classList.contains('sliding').should.equal(false);
794
+ $pangpang.classList.contains('sliding').should.equal(false);
795
+ switchSpy.should.have.been.calledWith($heihei);
796
+ topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
797
+ topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
798
+
799
+ switchSpy.restore();
800
+ });
801
+
802
+ it('showSiblingsEnd() accepts wrapped native visible and node payloads', function () {
803
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
804
+ const topEdgeEl = query('.topEdge', $heihei);
805
+ const switchSpy = sinon.spy(oc, 'switchHorizontalArrow');
806
+
807
+ addClasses($tiehua, 'sliding');
808
+ addClasses($pangpang, 'sliding');
809
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
810
+
811
+ oc.showSiblingsEnd({
812
+ originalEvent: {
813
+ node: $heihei,
814
+ visibleNodes: [$tiehua, $pangpang]
815
+ }
816
+ });
817
+
818
+ $tiehua.classList.contains('sliding').should.equal(false);
819
+ $pangpang.classList.contains('sliding').should.equal(false);
820
+ switchSpy.should.have.been.calledWith($heihei);
821
+ topEdgeEl.classList.contains(oc.options.icons.expandToUp).should.equal(false);
822
+ topEdgeEl.classList.contains(oc.options.icons.collapseToDown).should.equal(true);
823
+
824
+ switchSpy.restore();
825
+ });
826
+
827
+ it('showRelatedParentEnd() removes sliding from a native animated node payload', function () {
828
+ addClasses($sumiao, 'sliding');
829
+
830
+ oc.showRelatedParentEnd({ animatedNode: $sumiao });
831
+
832
+ $sumiao.classList.contains('sliding').should.equal(false);
833
+ });
834
+
835
+ it('showRelatedParentEnd() accepts a wrapped native animated node payload', function () {
836
+ addClasses($sumiao, 'sliding');
837
+
838
+ oc.showRelatedParentEnd({
839
+ originalEvent: {
840
+ target: $sumiao
841
+ }
842
+ });
843
+
844
+ $sumiao.classList.contains('sliding').should.equal(false);
845
+ });
846
+
847
+ it('showSiblings() accepts a native node and reveals the requested side with animation', function () {
848
+ const repaintSpy = sinon.spy(oc, 'repaint');
849
+
850
+ addClasses(closestElement($tiehua, '.hierarchy'), 'hidden isCollapsedSibling left-sibs');
851
+ addClasses($tiehua, 'slide-right');
852
+ addClasses(closestElement($heihei, '.hierarchy'), 'isSiblingsCollapsed left-sibs isAncestorsCollapsed');
853
+ addClasses($sumiao, 'hidden slide-down');
854
+
855
+ oc.showSiblings($heihei, 'left');
856
+
857
+ repaintSpy.should.have.been.calledWith($sumiao);
858
+ closestElement($tiehua, '.hierarchy').classList.contains('hidden').should.equal(false);
859
+ closestElement($tiehua, '.hierarchy').classList.contains('isCollapsedSibling').should.equal(false);
860
+ closestElement($heihei, '.hierarchy').classList.contains('left-sibs').should.equal(false);
861
+ closestElement($heihei, '.hierarchy').classList.contains('isSiblingsCollapsed').should.equal(false);
862
+ $tiehua.classList.contains('sliding').should.equal(true);
863
+ $tiehua.classList.contains('slide-right').should.equal(false);
864
+ $sumiao.classList.contains('hidden').should.equal(false);
865
+ $sumiao.classList.contains('sliding').should.equal(true);
866
+ $sumiao.classList.contains('slide-down').should.equal(false);
867
+
868
+ repaintSpy.restore();
869
+ });
870
+
329
871
  describe('hideSiblings()', function () {
330
872
  context('when passing only one parameter -- node', function () {
331
873
  it('should hide all the sibling nodes and their descendants', function () {
332
874
  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);
875
+ matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
876
+ matchesSelector($dandan, '.sliding.slide-right').should.be.true;
877
+ matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
878
+ matchesSelector($sandan, '.sliding.slide-left').should.be.true;
879
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed').should.be.true;
880
+ siblingElements(closestElement($heihei, '.hierarchy'), '.isChildrenCollapsed.isCollapsedSibling').should.lengthOf(2);
881
+ });
882
+
883
+ it('clears inAjax when a sibling hierarchy contains a spinner', function () {
884
+ const spinnerEl = document.createElement('i');
885
+
886
+ spinnerEl.className = 'spinner';
887
+ closestElement($tiehua, '.hierarchy').appendChild(spinnerEl);
888
+ setState(oc.chart, 'inAjax', true);
889
+
890
+ oc.hideSiblings($heihei);
891
+
892
+ getState(oc.chart, 'inAjax').should.be.false;
893
+ spinnerEl.remove();
339
894
  });
340
895
  });
341
896
 
342
897
  context('when passing two parameters -- node and direction', function () {
343
898
  it('hide the left side sibling nodes and their descendants', function () {
344
899
  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;
900
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.left-sibs').should.be.true;
901
+ matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
902
+ matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
903
+ matchesSelector($tiehua, '.sliding.slide-right').should.be.true;
904
+ matchesSelector($dandan, '.sliding.slide-right').should.be.true;
905
+ matchesSelector($pangpang, '.sliding').should.be.false;
351
906
  });
352
907
  it('hide the right side sibling nodes and their descendants', function () {
353
908
  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;
909
+ matchesSelector(closestElement($heihei, '.hierarchy'), '.isSiblingsCollapsed.right-sibs').should.be.true;
910
+ matchesSelector(closestElement($heihei, '.hierarchy').nextElementSibling, '.isChildrenCollapsed.isCollapsedSibling').should.be.true;
911
+ matchesSelector(closestElement($heihei, '.hierarchy').previousElementSibling, '.isChildrenCollapsed, .isCollapsedSibling').should.be.false;
912
+ matchesSelector($pangpang, '.sliding.slide-left').should.be.true;
913
+ matchesSelector($sandan, '.sliding.slide-left').should.be.true;
914
+ matchesSelector($tiehua, '.sliding').should.be.false;
360
915
  });
361
916
  });
362
917
  });
@@ -364,25 +919,29 @@ describe('orgchart -- unit tests', function () {
364
919
  describe('hideSiblingsEnd()', function () {
365
920
  context('when invoking transitionend event without specifying direction', function () {
366
921
  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');
922
+ const spy = sinon.spy(oc, 'hideSiblingsEnd');
923
+ const spy2 = sinon.spy(oc, 'isInAction');
924
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
925
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
926
+
371
927
  oc.hideSiblings($heihei);
372
- $tiehua.one('transitionend', {
928
+
929
+ setOneTimeListener($tiehua, 'transitionend', {
373
930
  'node': $heihei,
374
- 'nodeContainer': $nodeContainer,
931
+ 'nodeContainer': nodeContainerEl,
375
932
  'direction': undefined,
376
933
  'animatedNodes': $tiehua
377
934
  }, spy.bind(oc));
378
- $tiehua.trigger('transitionend');
935
+ triggerEvent($tiehua, 'transitionend');
379
936
  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);
937
+ matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
938
+ matchesSelector($dandan, '.slide-up:not(.sliding, .slide-right)').should.be.true;
939
+ matchesSelector($pangpang, '.slide-left:not(.sibling)').should.be.true;
940
+ matchesSelector($sandan, '.slide-up:not(.sliding, .slide-left)').should.be.true;
941
+ siblingElements(nodeContainerEl, '.hidden').should.lengthOf(2);
942
+ siblingElements(nodeContainerEl).reduce(function (count, siblingEl) {
943
+ return count + queryAll('.nodes.hidden', siblingEl).length;
944
+ }, 0).should.equal(2);
386
945
  spy2.should.have.been.calledWith($heihei);
387
946
  spy3.should.not.have.been.called;
388
947
  });
@@ -390,55 +949,1615 @@ describe('orgchart -- unit tests', function () {
390
949
 
391
950
  context('when invoking transitionend event with specifying direction', function () {
392
951
  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');
952
+ const spy = sinon.spy(oc, 'hideSiblingsEnd');
953
+ const spy2 = sinon.spy(oc, 'isInAction');
954
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
955
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
956
+
397
957
  oc.hideSiblings($heihei, 'left');
398
- $tiehua.one('transitionend', {
958
+
959
+ setOneTimeListener($tiehua, 'transitionend', {
399
960
  'node': $heihei,
400
- 'nodeContainer': $nodeContainer,
961
+ 'nodeContainer': nodeContainerEl,
401
962
  'direction': 'left',
402
963
  'animatedNodes': $tiehua
403
964
  }, spy.bind(oc));
404
- $tiehua.trigger('transitionend');
965
+ triggerEvent($tiehua, 'transitionend');
405
966
  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);
967
+ matchesSelector($tiehua, '.slide-right:not(.sliding)').should.be.true;
968
+ matchesSelector($dandan, '.slide-up:not(.slide-right)').should.be.true;
969
+ matchesSelector($pangpang, '.slide-left').should.be.false;
970
+ matchesSelector($sandan, '.slide-up').should.be.false;
971
+ previousSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
972
+ previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
973
+ return count + queryAll('.nodes.hidden', siblingEl).length;
974
+ }, 0).should.equal(1);
975
+ nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
976
+ return count + queryAll('.nodes.hidden', siblingEl).length;
977
+ }, 0).should.equal(0);
413
978
  spy2.should.have.been.calledWith($heihei);
414
979
  spy3.should.not.have.been.called;
415
980
  });
416
981
 
417
982
  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');
983
+ const spy2 = sinon.spy(oc, 'isInAction');
984
+ const spy3 = sinon.spy(oc, 'switchVerticalArrow');
985
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
986
+
422
987
  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);
988
+
989
+ oc.hideSiblingsEnd({
990
+ node: $heihei,
991
+ nodeContainer: nodeContainerEl,
992
+ direction: 'right',
993
+ animatedNodes: $tiehua
994
+ });
995
+ matchesSelector($tiehua, '.slide-right').should.be.false;
996
+ matchesSelector($dandan, '.slide-up').should.be.false;
997
+ matchesSelector($pangpang, '.slide-left:not(.slidiing)').should.be.true;
998
+ matchesSelector($sandan, '.slide-up:not(.slide-left)').should.be.true;
999
+ nextSiblings(nodeContainerEl, '.hidden').should.lengthOf(1);
1000
+ nextSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
1001
+ return count + queryAll('.nodes.hidden', siblingEl).length;
1002
+ }, 0).should.equal(1);
1003
+ previousSiblings(nodeContainerEl).reduce(function (count, siblingEl) {
1004
+ return count + queryAll('.nodes.hidden', siblingEl).length;
1005
+ }, 0).should.equal(0);
438
1006
  spy2.should.have.been.calledWith($heihei);
439
1007
  spy3.should.not.have.been.called;
440
1008
  });
1009
+
1010
+ it('accepts direct node, container, and animated payloads', function () {
1011
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
1012
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1013
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
1014
+
1015
+ oc.hideSiblings($heihei, 'left');
1016
+ addClasses($tiehua, 'sliding');
1017
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
1018
+ query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
1019
+
1020
+ oc.hideSiblingsEnd({
1021
+ node: $heihei,
1022
+ nodeContainer: nodeContainerEl,
1023
+ direction: 'left',
1024
+ animatedNodes: [$tiehua]
1025
+ });
1026
+
1027
+ $tiehua.classList.contains('sliding').should.equal(false);
1028
+ nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
1029
+ horizontalSpy.should.have.been.calledWith($heihei);
1030
+
1031
+ horizontalSpy.restore();
1032
+ });
1033
+
1034
+ it('accepts wrapped native node, container, and animated payloads', function () {
1035
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
1036
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1037
+ const nodeContainerEl = closestElement($heihei, '.hierarchy');
1038
+
1039
+ oc.hideSiblings($heihei, 'left');
1040
+ addClasses($tiehua, 'sliding');
1041
+ removeClasses(closestElement($tiehua, '.nodes'), 'hidden');
1042
+ query('.leftEdge', $heihei).classList.add(oc.options.icons.collapseToLeft);
1043
+
1044
+ oc.hideSiblingsEnd({
1045
+ originalEvent: {
1046
+ node: $heihei,
1047
+ nodeContainer: nodeContainerEl,
1048
+ direction: 'left',
1049
+ animatedNodes: [$tiehua]
1050
+ }
1051
+ });
1052
+
1053
+ $tiehua.classList.contains('sliding').should.equal(false);
1054
+ nodeContainerEl.previousElementSibling.classList.contains('hidden').should.equal(true);
1055
+ horizontalSpy.should.have.been.calledWith($heihei);
1056
+
1057
+ horizontalSpy.restore();
1058
+ });
441
1059
  });
442
1060
  });
443
1061
 
444
- });
1062
+ it('nodeEnterLeaveHandler() accepts a native node payload for relationship checks', function () {
1063
+ const spy = sinon.spy(oc, 'switchHorizontalArrow');
1064
+ oc.nodeEnterLeaveHandler({ type: 'mouseenter', node: $sumiao });
1065
+ spy.should.have.been.calledWith($sumiao);
1066
+ });
1067
+
1068
+ it('nodeEnterLeaveHandler() removes controls from a native node payload on mouseleave', function () {
1069
+ const nodeEl = $sumiao;
1070
+
1071
+ nodeEl.dispatchEvent(new window.MouseEvent('mouseenter'));
1072
+ nodeEl.querySelectorAll('.edge').length.should.equal(4);
1073
+
1074
+ oc.nodeEnterLeaveHandler({ type: 'mouseleave', node: nodeEl });
1075
+
1076
+ nodeEl.querySelectorAll('.edge').length.should.equal(0);
1077
+ });
1078
+
1079
+ it('nodeEnterLeaveHandler() accepts wrapped native node payloads', function () {
1080
+ const spy = sinon.spy(oc, 'switchHorizontalArrow');
1081
+
1082
+ oc.nodeEnterLeaveHandler({
1083
+ originalEvent: {
1084
+ type: 'mouseenter',
1085
+ currentTarget: $sumiao
1086
+ }
1087
+ });
1088
+
1089
+ spy.should.have.been.calledWith($sumiao);
1090
+ spy.restore();
1091
+ });
1092
+
1093
+ it('HideFirstParentEnd() accepts a native top edge payload', function () {
1094
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
1095
+ const topEdgeEl = query('.topEdge', $heihei);
1096
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
1097
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1098
+
1099
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
1100
+ oc.HideFirstParentEnd({ topEdge: topEdgeEl });
1101
+
1102
+ verticalSpy.should.have.been.calledWith(topEdgeEl);
1103
+ horizontalSpy.should.have.been.calledWith($heihei);
1104
+
1105
+ verticalSpy.restore();
1106
+ horizontalSpy.restore();
1107
+ });
1108
+
1109
+ it('HideFirstParentEnd() accepts a wrapped native top edge payload', function () {
1110
+ $heihei.dispatchEvent(new window.MouseEvent('mouseenter'));
1111
+ const topEdgeEl = query('.topEdge', $heihei);
1112
+ const verticalSpy = sinon.spy(oc, 'switchVerticalArrow');
1113
+ const horizontalSpy = sinon.spy(oc, 'switchHorizontalArrow');
1114
+
1115
+ topEdgeEl.classList.add(oc.options.icons.expandToUp);
1116
+ oc.HideFirstParentEnd({
1117
+ originalEvent: {
1118
+ topEdge: topEdgeEl
1119
+ }
1120
+ });
1121
+
1122
+ verticalSpy.should.have.been.calledWith(topEdgeEl);
1123
+ horizontalSpy.should.have.been.calledWith($heihei);
1124
+
1125
+ verticalSpy.restore();
1126
+ horizontalSpy.restore();
1127
+ });
1128
+
1129
+ it('nodeClickHandler() focuses the native node payload and clears previous focus', function () {
1130
+ addClasses($bomiao, 'focused');
1131
+
1132
+ oc.nodeClickHandler({ node: $sumiao });
1133
+
1134
+ $bomiao.classList.contains('focused').should.equal(false);
1135
+ $sumiao.classList.contains('focused').should.equal(true);
1136
+ });
1137
+
1138
+ it('nodeClickHandler() focuses a wrapped native node payload', function () {
1139
+ addClasses($bomiao, 'focused');
1140
+
1141
+ oc.nodeClickHandler({
1142
+ originalEvent: {
1143
+ currentTarget: $sumiao
1144
+ }
1145
+ });
1146
+
1147
+ $bomiao.classList.contains('focused').should.equal(false);
1148
+ $sumiao.classList.contains('focused').should.equal(true);
1149
+ });
1150
+
1151
+ it('init() clears focused nodes when clicking the chart background', function () {
1152
+ addClasses($bomiao, 'focused');
1153
+ addClasses($sumiao, 'focused');
1154
+
1155
+ oc.chart.dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
1156
+
1157
+ $bomiao.classList.contains('focused').should.equal(false);
1158
+ $sumiao.classList.contains('focused').should.equal(false);
1159
+ });
1160
+
1161
+ it('init() clears focused nodes from a wrapped native background click payload', function () {
1162
+ const wrappedClickEvent = new window.Event('click', { bubbles: true });
1163
+
1164
+ addClasses($bomiao, 'focused');
1165
+ addClasses($sumiao, 'focused');
1166
+
1167
+ wrappedClickEvent.originalEvent = {
1168
+ target: oc.chart
1169
+ };
1170
+ oc.chart.dispatchEvent(wrappedClickEvent);
1171
+
1172
+ $bomiao.classList.contains('focused').should.equal(false);
1173
+ $sumiao.classList.contains('focused').should.equal(false);
1174
+ });
1175
+
1176
+ it('init() appends the export button only once', function () {
1177
+ let oc2;
1178
+
1179
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1180
+ exportButtonEl.remove();
1181
+ });
1182
+
1183
+ oc2 = new OrgChart({
1184
+ chartContainer: '#chart-container',
1185
+ data: ds,
1186
+ exportButton: true
1187
+ });
1188
+
1189
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1190
+
1191
+ oc2.init();
1192
+
1193
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1194
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1195
+ exportButtonEl.remove();
1196
+ });
1197
+ });
1198
+
1199
+ it('attachExportButton() creates a native export button that calls export()', function () {
1200
+ const exportStub = sinon.stub(oc, 'export');
1201
+
1202
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1203
+ exportButtonEl.remove();
1204
+ });
1205
+
1206
+ oc.attachExportButton();
1207
+ document.querySelectorAll('.oc-export-btn').length.should.equal(1);
1208
+ document.querySelector('.oc-export-btn').dispatchEvent(new window.MouseEvent('click', { bubbles: true }));
1209
+ exportStub.should.have.been.calledOnce;
1210
+
1211
+ exportStub.restore();
1212
+ Array.from(document.querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
1213
+ exportButtonEl.remove();
1214
+ });
1215
+ });
1216
+
1217
+ it('topEdgeClickHandler() passes a native node to hideParent()', function () {
1218
+ const spy = sinon.spy(oc, 'hideParent');
1219
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1220
+
1221
+ oc.topEdgeClickHandler({ edge: query('.topEdge', $heihei), node: $heihei });
1222
+
1223
+ spy.should.have.been.calledWith($heihei);
1224
+ spy2.should.have.been.called;
1225
+ });
1226
+
1227
+ it('topEdgeClickHandler() accepts wrapped native edge payloads', function () {
1228
+ const spy = sinon.spy(oc, 'hideParent');
1229
+
1230
+ oc.topEdgeClickHandler({
1231
+ originalEvent: {
1232
+ currentTarget: $heihei,
1233
+ target: query('.topEdge', $heihei)
1234
+ }
1235
+ });
1236
+
1237
+ spy.should.have.been.calledWith($heihei);
1238
+ spy.restore();
1239
+ });
1240
+
1241
+ it('bottomEdgeClickHandler() passes a native node to hideChildren()', function () {
1242
+ const spy = sinon.spy(oc, 'hideChildren');
1243
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1244
+
1245
+ oc.bottomEdgeClickHandler({ edge: query('.bottomEdge', $sumiao), node: $sumiao });
1246
+
1247
+ spy.should.have.been.calledWith($sumiao);
1248
+ spy2.should.have.been.called;
1249
+ });
1250
+
1251
+ it('bottomEdgeClickHandler() accepts wrapped native node payloads', function () {
1252
+ const spy = sinon.spy(oc, 'hideChildren');
1253
+
1254
+ oc.bottomEdgeClickHandler({
1255
+ originalEvent: {
1256
+ currentTarget: $sumiao
1257
+ }
1258
+ });
1259
+
1260
+ spy.should.have.been.calledWith($sumiao);
1261
+ spy.restore();
1262
+ });
1263
+
1264
+ it('hEdgeClickHandler() passes a native node to hideSiblings()', function () {
1265
+ const spy = sinon.spy(oc, 'hideSiblings');
1266
+ const spy2 = sinon.spy(oc, 'triggerHideEvent');
1267
+
1268
+ oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
1269
+
1270
+ spy.should.have.been.calledWith($sumiao);
1271
+ spy2.should.have.been.called;
1272
+ });
1273
+
1274
+ it('hEdgeClickHandler() accepts wrapped native edge payloads', function () {
1275
+ const spy = sinon.spy(oc, 'hideSiblings');
1276
+
1277
+ oc.hEdgeClickHandler({
1278
+ originalEvent: {
1279
+ currentTarget: $sumiao,
1280
+ target: query('.leftEdge', $sumiao)
1281
+ }
1282
+ });
1283
+
1284
+ spy.should.have.been.calledWith($sumiao);
1285
+ spy.restore();
1286
+ });
1287
+
1288
+ it('hEdgeClickHandler() restores the collapsed parent after topEdgeClickHandler()', function () {
1289
+ oc.topEdgeClickHandler({ edge: query('.topEdge', $sumiao), node: $sumiao });
1290
+ triggerEvent($bomiao, 'transitionend');
1291
+ triggerEvent($laolao, 'transitionend');
1292
+
1293
+ oc.hEdgeClickHandler({ edge: query('.leftEdge', $sumiao), node: $sumiao });
1294
+
1295
+ closestElement($sumiao, '.hierarchy').classList.contains('isAncestorsCollapsed').should.equal(false);
1296
+ $laolao.classList.contains('hidden').should.equal(false);
1297
+ $laolao.classList.contains('slide-down').should.equal(false);
1298
+ $laolao.classList.contains('sliding').should.equal(true);
1299
+ });
1300
+
1301
+ it('startLoading() and endLoading() accept a native edge node', function () {
1302
+ $sumiao.dispatchEvent(new window.MouseEvent('mouseenter'));
1303
+ const edgeEl = query('.bottomEdge', $sumiao);
1304
+
1305
+ oc.startLoading(edgeEl).should.be.true;
1306
+ matchesSelector(edgeEl, '.hidden').should.be.true;
1307
+ queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(1);
1308
+ getState(oc.chart, 'inAjax').should.be.true;
1309
+
1310
+ oc.endLoading(edgeEl);
1311
+ matchesSelector(edgeEl, '.hidden').should.be.false;
1312
+ queryAll('.spinner', edgeEl.parentElement).should.have.lengthOf(0);
1313
+ getState(oc.chart, 'inAjax').should.be.false;
1314
+ });
1315
+
1316
+ it('startLoading() and endLoading() toggle native export buttons', function () {
1317
+ $sumiao.dispatchEvent(new window.MouseEvent('mouseenter'));
1318
+ const edgeEl = query('.bottomEdge', $sumiao);
1319
+ const exportButtonEl = document.createElement('button');
1320
+
1321
+ exportButtonEl.className = 'oc-export-btn';
1322
+ document.body.appendChild(exportButtonEl);
1323
+
1324
+ oc.startLoading(edgeEl);
1325
+ exportButtonEl.disabled.should.equal(true);
1326
+
1327
+ oc.endLoading(edgeEl);
1328
+ exportButtonEl.disabled.should.equal(false);
1329
+
1330
+ exportButtonEl.remove();
1331
+ });
1332
+
1333
+ it('stopAjax() accepts a native node level element', function () {
1334
+ const nodeLevelEl = oc.getChildren($sumiao)[0].closest('.nodes');
1335
+ const spinnerEl = document.createElement('i');
1336
+
1337
+ spinnerEl.className = 'spinner';
1338
+ nodeLevelEl.appendChild(spinnerEl);
1339
+ setState(oc.chart, 'inAjax', true);
1340
+
1341
+ oc.stopAjax(nodeLevelEl);
1342
+
1343
+ getState(oc.chart, 'inAjax').should.be.false;
1344
+ spinnerEl.remove();
1345
+ });
1346
+
1347
+ it('backToLooseHandler() and backToCompactHandler() toggle compact mode classes', function () {
1348
+ const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
1349
+
1350
+ $container.appendChild(compactNodeEl);
1351
+
1352
+ oc.backToLooseHandler({ compactNode: compactNodeEl });
1353
+ matchesSelector(compactNodeEl, '.looseMode').should.be.true;
1354
+ matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.true;
1355
+ matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.false;
1356
+
1357
+ oc.backToCompactHandler({ compactNode: compactNodeEl });
1358
+ matchesSelector(compactNodeEl, '.looseMode').should.be.false;
1359
+ matchesSelector(query('.backToCompactSymbol', compactNodeEl), '.hidden').should.be.true;
1360
+ matchesSelector(query('.backToLooseSymbol', compactNodeEl), '.hidden').should.be.false;
1361
+ });
1362
+
1363
+ it('backToLooseHandler() and backToCompactHandler() accept wrapped native compact-node payloads', function () {
1364
+ const compactNodeEl = createElementFromHtml('<div class="node compact"><i class="backToCompactSymbol hidden"></i><i class="backToLooseSymbol"></i></div>');
1365
+
1366
+ $container.appendChild(compactNodeEl);
1367
+
1368
+ oc.backToLooseHandler({
1369
+ originalEvent: {
1370
+ currentTarget: compactNodeEl
1371
+ }
1372
+ });
1373
+ matchesSelector(compactNodeEl, '.looseMode').should.be.true;
1374
+
1375
+ oc.backToCompactHandler({
1376
+ originalEvent: {
1377
+ currentTarget: compactNodeEl
1378
+ }
1379
+ });
1380
+ matchesSelector(compactNodeEl, '.looseMode').should.be.false;
1381
+ });
1382
+
1383
+ it('toggleVNodes() expands vertical descendants from a native toggle button payload', function () {
1384
+ const spy = sinon.spy(oc, 'repaint');
1385
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1386
+ const descWrapperEl = createElementFromHtml('<ul class="nodes hidden"><li class="hierarchy"><div class="node slide-up">child</div></li></ul>');
1387
+ let childNodeEl;
1388
+
1389
+ $container.appendChild(toggleHostEl);
1390
+ $container.appendChild(descWrapperEl);
1391
+ childNodeEl = query('.node', descWrapperEl);
1392
+
1393
+ oc.toggleVNodes({ toggleButton: query('.toggleBtn', toggleHostEl) });
1394
+
1395
+ spy.should.have.been.calledWith(childNodeEl);
1396
+ matchesSelector(descWrapperEl, '.hidden').should.be.false;
1397
+ matchesSelector(childNodeEl, '.sliding').should.be.true;
1398
+ matchesSelector(childNodeEl, '.slide-up').should.be.false;
1399
+ });
1400
+
1401
+ it('toggleVNodes() accepts a wrapped native toggle button payload', function () {
1402
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1403
+ const descWrapperEl = createElementFromHtml('<ul class="nodes hidden"><li class="hierarchy"><div class="node slide-up">child</div></li></ul>');
1404
+
1405
+ $container.appendChild(toggleHostEl);
1406
+ $container.appendChild(descWrapperEl);
1407
+
1408
+ oc.toggleVNodes({
1409
+ originalEvent: {
1410
+ target: query('.toggleBtn', toggleHostEl)
1411
+ }
1412
+ });
1413
+
1414
+ matchesSelector(descWrapperEl, '.hidden').should.be.false;
1415
+ });
1416
+
1417
+ it('toggleVNodes() collapses vertical descendants from a native toggle button payload', function () {
1418
+ const toggleHostEl = createElementFromHtml('<div class="node"><i class="toggleBtn"></i></div>');
1419
+ 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>');
1420
+ let nestedToggleBtnEl;
1421
+ let childNodeEl;
1422
+
1423
+ $container.appendChild(toggleHostEl);
1424
+ $container.appendChild(descWrapperEl);
1425
+ nestedToggleBtnEl = query('.node .toggleBtn', descWrapperEl);
1426
+ childNodeEl = query('.node', descWrapperEl);
1427
+
1428
+ oc.toggleVNodes({ toggleButton: query('.toggleBtn', toggleHostEl) });
1429
+
1430
+ matchesSelector(childNodeEl, '.sliding.slide-up').should.be.true;
1431
+ nestedToggleBtnEl.classList.contains('oci-plus-square').should.be.false;
1432
+ nestedToggleBtnEl.classList.contains('oci-minus-square').should.be.false;
1433
+ });
1434
+
1435
+ it('expandVNodesEnd() accepts direct vNodes payloads', function () {
1436
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1437
+ const childNodeEl = query('.node', descWrapperEl);
1438
+
1439
+ $container.appendChild(descWrapperEl);
1440
+
1441
+ oc.expandVNodesEnd({ vNodes: [childNodeEl] });
1442
+
1443
+ childNodeEl.classList.contains('sliding').should.equal(false);
1444
+ });
1445
+
1446
+ it('expandVNodesEnd() accepts wrapped native vNodes payloads', function () {
1447
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1448
+ const childNodeEl = query('.node', descWrapperEl);
1449
+
1450
+ $container.appendChild(descWrapperEl);
1451
+
1452
+ oc.expandVNodesEnd({
1453
+ originalEvent: {
1454
+ vNodes: [childNodeEl]
1455
+ }
1456
+ });
1457
+
1458
+ childNodeEl.classList.contains('sliding').should.equal(false);
1459
+ });
1460
+
1461
+ it('collapseVNodesEnd() accepts direct vNodes payloads', function () {
1462
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1463
+ const childNodeEl = query('.node', descWrapperEl);
1464
+
1465
+ $container.appendChild(descWrapperEl);
1466
+
1467
+ oc.collapseVNodesEnd({ vNodes: [childNodeEl] });
1468
+
1469
+ childNodeEl.classList.contains('sliding').should.equal(false);
1470
+ descWrapperEl.classList.contains('hidden').should.equal(true);
1471
+ });
1472
+
1473
+ it('collapseVNodesEnd() accepts wrapped native vNodes payloads', function () {
1474
+ const descWrapperEl = createElementFromHtml('<ul class="nodes"><li class="hierarchy"><div class="node sliding">child</div></li></ul>');
1475
+ const childNodeEl = query('.node', descWrapperEl);
1476
+
1477
+ $container.appendChild(descWrapperEl);
1478
+
1479
+ oc.collapseVNodesEnd({
1480
+ originalEvent: {
1481
+ vNodes: [childNodeEl]
1482
+ }
1483
+ });
1484
+
1485
+ childNodeEl.classList.contains('sliding').should.equal(false);
1486
+ descWrapperEl.classList.contains('hidden').should.equal(true);
1487
+ });
1488
+
1489
+ it('filterAllowedDropNodes() accepts a native dragged node', function () {
1490
+ oc.filterAllowedDropNodes($heihei);
1491
+
1492
+ getState(oc.chart, 'dragged').should.equal($heihei);
1493
+ $laolao.classList.contains('allowedDrop').should.be.true;
1494
+ queryAll('.allowedDrop', oc.chart).length.should.be.above(0);
1495
+ });
1496
+
1497
+ it('filterAllowedDropNodes() passes native node arguments to dropCriteria', function () {
1498
+ const dropCriteria = sinon.stub().returns(true);
1499
+
1500
+ oc.options.dropCriteria = dropCriteria;
1501
+
1502
+ oc.filterAllowedDropNodes($heihei);
1503
+
1504
+ dropCriteria.should.have.been.called;
1505
+ dropCriteria.firstCall.args[0].should.equal($heihei);
1506
+ dropCriteria.firstCall.args[1].should.equal($sumiao);
1507
+ dropCriteria.firstCall.args[2].should.have.property('nodeType', 1);
1508
+ });
1509
+
1510
+ it('dragstartHandler() passes a native dragged node payload to filterAllowedDropNodes()', function () {
1511
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1512
+ const dataTransfer = { setData: sinon.spy() };
1513
+
1514
+ oc.dragstartHandler({ draggedNode: $heihei, originalEvent: { dataTransfer: dataTransfer } });
1515
+
1516
+ dataTransfer.setData.should.have.been.called;
1517
+ spy.should.have.been.calledWith($heihei);
1518
+ });
1519
+
1520
+ it('dragstartHandler() accepts a wrapped native dragged node payload', function () {
1521
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1522
+ const dataTransfer = { setData: sinon.spy() };
1523
+
1524
+ oc.dragstartHandler({
1525
+ originalEvent: {
1526
+ target: $heihei,
1527
+ dataTransfer: dataTransfer
1528
+ }
1529
+ });
1530
+
1531
+ dataTransfer.setData.should.have.been.called;
1532
+ spy.should.have.been.calledWith($heihei);
1533
+ spy.restore();
1534
+ });
1535
+
1536
+ it('createGhostNode() accepts a native source node payload and creates a ghost image', function () {
1537
+ const setDragImageSpy = sinon.spy();
1538
+ const sourceNodeEl = $heihei;
1539
+
1540
+ oc.chart.style.transform = 'matrix(1, 0, 0, 1, 10, 20)';
1541
+ sourceNodeEl.getBoundingClientRect = function () {
1542
+ return { width: 120, height: 40, left: 10, top: 20 };
1543
+ };
1544
+
1545
+ oc.createGhostNode({
1546
+ sourceNode: sourceNodeEl,
1547
+ originalEvent: {
1548
+ offsetX: 5,
1549
+ offsetY: 6,
1550
+ dataTransfer: { setDragImage: setDragImageSpy }
1551
+ }
1552
+ });
1553
+
1554
+ const ghostNodeEl = oc.chart.querySelector('.ghost-node');
1555
+ should.exist(ghostNodeEl);
1556
+ ghostNodeEl.getAttribute('width').should.equal('120');
1557
+ ghostNodeEl.getAttribute('height').should.equal('40');
1558
+ setDragImageSpy.should.have.been.calledOnce;
1559
+ setDragImageSpy.firstCall.args[0].should.equal(ghostNodeEl);
1560
+ });
1561
+
1562
+ it('createGhostNode() accepts a wrapped native source node payload', function () {
1563
+ const setDragImageSpy = sinon.spy();
1564
+ const sourceNodeEl = $heihei;
1565
+
1566
+ oc.chart.style.transform = 'matrix(1, 0, 0, 1, 10, 20)';
1567
+ sourceNodeEl.getBoundingClientRect = function () {
1568
+ return { width: 120, height: 40, left: 10, top: 20 };
1569
+ };
1570
+
1571
+ oc.createGhostNode({
1572
+ originalEvent: {
1573
+ target: sourceNodeEl,
1574
+ offsetX: 5,
1575
+ offsetY: 6,
1576
+ dataTransfer: { setDragImage: setDragImageSpy }
1577
+ }
1578
+ });
1579
+
1580
+ should.exist(oc.chart.querySelector('.ghost-node'));
1581
+ setDragImageSpy.should.have.been.calledOnce;
1582
+ });
1583
+
1584
+ it('dragoverHandler() accepts a native drop zone payload', function () {
1585
+ const disallowedEvent = {
1586
+ dropZone: $bomiao,
1587
+ originalEvent: { dataTransfer: { dropEffect: 'move' } },
1588
+ preventDefault: sinon.spy()
1589
+ };
1590
+ disallowedEvent.originalEvent.preventDefault = disallowedEvent.preventDefault;
1591
+ const allowedEvent = {
1592
+ dropZone: addClasses($hongmiao, 'allowedDrop'),
1593
+ originalEvent: { dataTransfer: { dropEffect: 'move' } },
1594
+ preventDefault: sinon.spy()
1595
+ };
1596
+ allowedEvent.originalEvent.preventDefault = allowedEvent.preventDefault;
1597
+
1598
+ oc.dragoverHandler(disallowedEvent);
1599
+ oc.dragoverHandler(allowedEvent);
1600
+
1601
+ disallowedEvent.originalEvent.dataTransfer.dropEffect.should.equal('none');
1602
+ disallowedEvent.preventDefault.should.not.have.been.called;
1603
+ allowedEvent.preventDefault.should.have.been.calledOnce;
1604
+ });
1605
+
1606
+ it('dragoverHandler() accepts wrapped native drop-zone payloads', function () {
1607
+ const wrappedEvent = {
1608
+ originalEvent: {
1609
+ currentTarget: addClasses($hongmiao, 'allowedDrop'),
1610
+ dataTransfer: { dropEffect: 'move' },
1611
+ preventDefault: sinon.spy()
1612
+ }
1613
+ };
1614
+
1615
+ oc.dragoverHandler(wrappedEvent);
1616
+
1617
+ wrappedEvent.originalEvent.preventDefault.should.have.been.calledOnce;
1618
+ });
1619
+
1620
+ it('dragendHandler() removes allowedDrop from all nodes', function () {
1621
+ addClasses($bomiao, 'allowedDrop');
1622
+ addClasses($hongmiao, 'allowedDrop');
1623
+
1624
+ oc.dragendHandler({});
1625
+
1626
+ queryAll('.allowedDrop', oc.chart).should.have.lengthOf(0);
1627
+ });
1628
+
1629
+ it('dropHandler() accepts a native drop zone for non-node drags', async function () {
1630
+ const spy = sinon.spy();
1631
+ const foreignEl = createElementFromHtml('<div class="foreign-item"></div>');
1632
+
1633
+ oc.chart.addEventListener('otherdropped.orgchart', spy);
1634
+ setState(oc.chart, 'dragged', foreignEl);
1635
+
1636
+ await oc.dropHandler({ dropZone: $bomiao });
1637
+
1638
+ spy.should.have.been.calledOnce;
1639
+ spy.firstCall.args[0].type.should.equal('otherdropped.orgchart');
1640
+ spy.firstCall.args[0].detail.dropZone.should.equal($bomiao);
1641
+ });
1642
+
1643
+ it('dropHandler() accepts a wrapped native drop zone for non-node drags', async function () {
1644
+ const spy = sinon.spy();
1645
+ const foreignEl = createElementFromHtml('<div class="foreign-item"></div>');
1646
+
1647
+ oc.chart.addEventListener('otherdropped.orgchart', spy);
1648
+ setState(oc.chart, 'dragged', foreignEl);
1649
+
1650
+ await oc.dropHandler({
1651
+ originalEvent: {
1652
+ currentTarget: $bomiao
1653
+ }
1654
+ });
1655
+
1656
+ spy.should.have.been.calledOnce;
1657
+ spy.firstCall.args[0].detail.dropZone.should.equal($bomiao);
1658
+ });
1659
+
1660
+ it('dropHandler() accepts a native drop zone for node drags', async function () {
1661
+ const originalJSONDigger = global.JSONDigger;
1662
+
1663
+ global.JSONDigger = function () {
1664
+ return {
1665
+ findOneNode: function () {
1666
+ return null;
1667
+ }
1668
+ };
1669
+ };
1670
+ setState(oc.chart, 'dragged', $bomiao);
1671
+ addClasses($hongmiao, 'allowedDrop');
1672
+
1673
+ await oc.dropHandler({ dropZone: $hongmiao });
1674
+
1675
+ queryAll('#n2', firstSibling($hongmiao, '.nodes')).should.lengthOf(1);
1676
+ childElements(firstSibling($laolao, '.nodes'), '.hierarchy').should.lengthOf(2);
1677
+ global.JSONDigger = originalJSONDigger;
1678
+ });
1679
+
1680
+ it('dropHandler() keeps nodedrop payloads as native nodes', async function () {
1681
+ const originalJSONDigger = global.JSONDigger;
1682
+ const spy = sinon.spy();
1683
+
1684
+ global.JSONDigger = function () {
1685
+ return {
1686
+ findOneNode: function () {
1687
+ return null;
1688
+ }
1689
+ };
1690
+ };
1691
+ oc.chart.addEventListener('nodedrop.orgchart', spy);
1692
+ setState(oc.chart, 'dragged', $bomiao);
1693
+ addClasses($hongmiao, 'allowedDrop');
1694
+
1695
+ await oc.dropHandler({ dropZone: $hongmiao });
1696
+
1697
+ spy.should.have.been.calledOnce;
1698
+ spy.firstCall.args[0].type.should.equal('nodedrop.orgchart');
1699
+ spy.firstCall.args[0].detail.draggedNode.should.equal($bomiao);
1700
+ spy.firstCall.args[0].detail.dragZone.should.equal($laolao);
1701
+ spy.firstCall.args[0].detail.dropZone.should.equal($hongmiao);
1702
+ global.JSONDigger = originalJSONDigger;
1703
+ });
1704
+
1705
+ it('dropHandler() removes source child container when dragged node was the only child', async function () {
1706
+ const originalJSONDigger = global.JSONDigger;
1707
+
1708
+ global.JSONDigger = function () {
1709
+ return {
1710
+ findOneNode: function () {
1711
+ return null;
1712
+ }
1713
+ };
1714
+ };
1715
+ setState(oc.chart, 'dragged', $dandan);
1716
+ addClasses($hongmiao, 'allowedDrop');
1717
+
1718
+ await oc.dropHandler({ dropZone: $hongmiao });
1719
+
1720
+ queryAll('#n8', firstSibling($hongmiao, '.nodes')).should.lengthOf(1);
1721
+ siblingElements($tiehua, '.nodes').should.lengthOf(0);
1722
+ childElements($tiehua, '.bottomEdge, .parentNodeSymbol').should.lengthOf(0);
1723
+ global.JSONDigger = originalJSONDigger;
1724
+ });
1725
+
1726
+ it('touchmoveHandler() passes a native dragged node payload to filterAllowedDropNodes()', function () {
1727
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1728
+ const createDragImageStub = sinon.stub(oc, 'createDragImage').returns(document.createElement('div'));
1729
+ const moveDragImageStub = sinon.stub(oc, 'moveDragImage');
1730
+ const originalElementFromPoint = document.elementFromPoint;
1731
+
1732
+ document.elementFromPoint = function () {
1733
+ return $bomiao;
1734
+ };
1735
+
1736
+ oc.touchHandled = true;
1737
+ oc.touchMoved = false;
1738
+ oc.touchmoveHandler({
1739
+ draggedNode: $heihei,
1740
+ touches: [{ clientX: 0, clientY: 0 }],
1741
+ preventDefault: function () {}
1742
+ });
1743
+
1744
+ spy.should.have.been.calledWith($heihei);
1745
+ createDragImageStub.should.have.been.called;
1746
+ moveDragImageStub.should.have.been.called;
1747
+ oc.touchTargetNode.should.equal($bomiao);
1748
+
1749
+ document.elementFromPoint = originalElementFromPoint;
1750
+ moveDragImageStub.restore();
1751
+ createDragImageStub.restore();
1752
+ });
1753
+
1754
+ it('touchmoveHandler() accepts wrapped native touch payloads', function () {
1755
+ const spy = sinon.spy(oc, 'filterAllowedDropNodes');
1756
+ const createDragImageStub = sinon.stub(oc, 'createDragImage').returns(document.createElement('div'));
1757
+ const moveDragImageStub = sinon.stub(oc, 'moveDragImage');
1758
+ const originalElementFromPoint = document.elementFromPoint;
1759
+
1760
+ document.elementFromPoint = function () {
1761
+ return $bomiao;
1762
+ };
1763
+
1764
+ oc.touchHandled = true;
1765
+ oc.touchMoved = false;
1766
+ oc.touchmoveHandler({
1767
+ originalEvent: {
1768
+ currentTarget: $heihei,
1769
+ touches: [{ clientX: 0, clientY: 0 }],
1770
+ preventDefault: function () {}
1771
+ }
1772
+ });
1773
+
1774
+ spy.should.have.been.calledWith($heihei);
1775
+ createDragImageStub.should.have.been.called;
1776
+ moveDragImageStub.should.have.been.called;
1777
+ oc.touchTargetNode.should.equal($bomiao);
1778
+
1779
+ document.elementFromPoint = originalElementFromPoint;
1780
+ moveDragImageStub.restore();
1781
+ createDragImageStub.restore();
1782
+ spy.restore();
1783
+ });
1784
+
1785
+ it('touchstartHandler() accepts a wrapped native touch payload', function () {
1786
+ const preventDefaultSpy = sinon.spy();
1787
+
1788
+ oc.touchHandled = false;
1789
+ oc.touchMoved = true;
1790
+
1791
+ oc.touchstartHandler({
1792
+ originalEvent: {
1793
+ touches: [{ clientX: 0, clientY: 0 }],
1794
+ preventDefault: preventDefaultSpy
1795
+ }
1796
+ });
1797
+
1798
+ oc.touchHandled.should.equal(true);
1799
+ oc.touchMoved.should.equal(false);
1800
+ preventDefaultSpy.should.have.been.calledOnce;
1801
+ });
1802
+
1803
+ it('getTouchPoint() accepts a wrapped native touch payload', function () {
1804
+ const touchPoint = oc.getTouchPoint({
1805
+ originalEvent: {
1806
+ touches: [{ clientX: 12, clientY: 34 }]
1807
+ }
1808
+ });
1809
+
1810
+ touchPoint.x.should.equal(12);
1811
+ touchPoint.y.should.equal(34);
1812
+ });
1813
+
1814
+ it('touchendHandler() passes a native drop zone to dropHandler() after touch drag', function () {
1815
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1816
+ const dropHandlerStub = sinon.stub(oc, 'dropHandler');
1817
+ const dragendHandlerStub = sinon.stub(oc, 'dragendHandler');
1818
+
1819
+ oc.touchHandled = true;
1820
+ oc.touchMoved = true;
1821
+ oc.touchTargetNode = $bomiao;
1822
+
1823
+ oc.touchendHandler({});
1824
+
1825
+ destroyDragImageStub.should.have.been.calledOnce;
1826
+ dropHandlerStub.should.have.been.calledOnce;
1827
+ dropHandlerStub.firstCall.args[0].dropZone.should.equal($bomiao);
1828
+ dragendHandlerStub.should.have.been.calledOnce;
1829
+ should.equal(oc.touchTargetNode, null);
1830
+ oc.touchHandled.should.equal(false);
1831
+
1832
+ dragendHandlerStub.restore();
1833
+ dropHandlerStub.restore();
1834
+ destroyDragImageStub.restore();
1835
+ });
1836
+
1837
+ it('touchendHandler() dispatches a native click when touch did not move', function () {
1838
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1839
+ const clickSpy = sinon.spy();
1840
+ const targetEl = document.createElement('div');
1841
+
1842
+ targetEl.addEventListener('click', clickSpy);
1843
+ oc.touchHandled = true;
1844
+ oc.touchMoved = false;
1845
+
1846
+ oc.touchendHandler({
1847
+ target: targetEl,
1848
+ changedTouches: [{ screenX: 1, screenY: 2, clientX: 3, clientY: 4 }],
1849
+ ctrlKey: false,
1850
+ altKey: false,
1851
+ shiftKey: false,
1852
+ metaKey: false
1853
+ });
1854
+
1855
+ destroyDragImageStub.should.have.been.calledOnce;
1856
+ clickSpy.should.have.been.calledOnce;
1857
+ clickSpy.firstCall.args[0].type.should.equal('click');
1858
+ clickSpy.firstCall.args[0].clientX.should.equal(3);
1859
+ clickSpy.firstCall.args[0].clientY.should.equal(4);
1860
+ oc.touchHandled.should.equal(false);
1861
+
1862
+ destroyDragImageStub.restore();
1863
+ });
1864
+
1865
+ it('touchendHandler() accepts wrapped native touchend payloads', function () {
1866
+ const destroyDragImageStub = sinon.stub(oc, 'destroyDragImage');
1867
+ const clickSpy = sinon.spy();
1868
+ const targetEl = document.createElement('div');
1869
+
1870
+ targetEl.addEventListener('click', clickSpy);
1871
+ oc.touchHandled = true;
1872
+ oc.touchMoved = false;
1873
+
1874
+ oc.touchendHandler({
1875
+ originalEvent: {
1876
+ target: targetEl,
1877
+ changedTouches: [{ screenX: 1, screenY: 2, clientX: 3, clientY: 4 }],
1878
+ ctrlKey: false,
1879
+ altKey: false,
1880
+ shiftKey: false,
1881
+ metaKey: false
1882
+ }
1883
+ });
1884
+
1885
+ destroyDragImageStub.should.have.been.calledOnce;
1886
+ clickSpy.should.have.been.calledOnce;
1887
+ oc.touchHandled.should.equal(false);
1888
+
1889
+ destroyDragImageStub.restore();
1890
+ });
1891
+
1892
+ it('getUpperLevel() and getLowerLevel() accept native nodes', function () {
1893
+ oc.getUpperLevel($laolao).should.equal(1);
1894
+ oc.getUpperLevel($dandan).should.equal(4);
1895
+ oc.getLowerLevel($laolao).should.equal(4);
1896
+ oc.getLowerLevel($dandan).should.equal(1);
1897
+ });
1898
+
1899
+ it('getLevelOrderNodes() accepts a native root node', function () {
1900
+ const levels = oc.getLevelOrderNodes($laolao);
1901
+
1902
+ levels.should.have.lengthOf(4);
1903
+ levels[0][0].should.equal($laolao);
1904
+ levels[1].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n2', 'n3', 'n4']);
1905
+ levels[2].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n5', 'n6', 'n7']);
1906
+ levels[3].map(function (nodeEl) { return nodeEl.id; }).should.deep.equal(['n8', 'n9', 'n10']);
1907
+ });
1908
+
1909
+ it('isInAction() tracks node interaction independently of arrow classes', function () {
1910
+ const nodeEl = $sumiao;
1911
+
1912
+ oc.isInAction(nodeEl).should.equal(false);
1913
+ nodeEl.dispatchEvent(new window.MouseEvent('mouseenter'));
1914
+ oc.isInAction(nodeEl).should.equal(true);
1915
+
1916
+ nodeEl.querySelector('.topEdge').className = 'edge topEdge';
1917
+ oc.isInAction(nodeEl).should.equal(true);
1918
+ nodeEl.dispatchEvent(new window.MouseEvent('mouseleave'));
1919
+ oc.isInAction(nodeEl).should.equal(false);
1920
+ });
1921
+
1922
+ it('switchHorizontalArrow() accepts a native node and toggles edge classes from native siblings', function () {
1923
+ const nodeEl = $sumiao;
1924
+ nodeEl.dispatchEvent(new window.MouseEvent('mouseenter'));
1925
+ const leftEdgeEl = nodeEl.querySelector('.leftEdge');
1926
+ const rightEdgeEl = nodeEl.querySelector('.rightEdge');
1927
+
1928
+ oc.switchHorizontalArrow(nodeEl);
1929
+ leftEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(true);
1930
+ leftEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
1931
+ rightEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(true);
1932
+ rightEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
1933
+
1934
+ siblingElements(closestElement($sumiao, '.hierarchy')).forEach(function (siblingEl) {
1935
+ addClasses(siblingEl, 'hidden');
1936
+ });
1937
+ oc.switchHorizontalArrow(nodeEl);
1938
+
1939
+ leftEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(false);
1940
+ leftEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(true);
1941
+ rightEdgeEl.classList.contains(oc.options.icons.collapseToLeft).should.equal(false);
1942
+ rightEdgeEl.classList.contains(oc.options.icons.expandToRight).should.equal(true);
1943
+ });
1944
+
1945
+ it('switchVerticalArrow() accepts a native edge element and toggles vertical arrow classes', function () {
1946
+ $sumiao.dispatchEvent(new window.MouseEvent('mouseenter'));
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, touches: [{ clientX: 0, clientY: 0 }, { clientX: 3, clientY: 4 }] });
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', 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', level: 1 });
2301
+ hierarchyEl.appendChild(nodeEl);
2302
+
2303
+ verticalChart.buildInferiorNodes(hierarchyEl, nodeEl, {
2304
+ collapsed: true,
2305
+ children: [{ id: 'n12', name: 'Child' }]
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', 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' }]
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', outsider: false },
2343
+ {
2344
+ id: 'n12',
2345
+ name: 'Spouse B',
2346
+ outsider: true,
2347
+ children: [[{ id: 'n13', name: 'Child' }]]
2348
+ }
2349
+ ]]);
2350
+
2351
+ wrapperEl = hierarchyEl.firstElementChild;
2352
+
2353
+ should.exist(wrapperEl);
2354
+ wrapperEl.classList.contains('couple').should.equal(true);
2355
+ Array.from(wrapperEl.children).filter(function (childEl) {
2356
+ return childEl.classList && childEl.classList.contains('node');
2357
+ }).length.should.equal(2);
2358
+ wrapperEl.querySelector('#n12').should.not.equal(null);
2359
+ wrapperEl.querySelector('.nodes').should.not.equal(null);
2360
+ });
2361
+
2362
+ it('addAncestors() wraps the original root content into a different hierarchy from the new ancestor', function () {
2363
+ let ancestorNodeEl;
2364
+ let originalRootNodeEl;
2365
+
2366
+ oc.addAncestors({ id: 'n0', name: 'Lao Ye' }, 'n0');
2367
+
2368
+ ancestorNodeEl = document.getElementById('n0');
2369
+ originalRootNodeEl = document.getElementById('n1');
2370
+
2371
+ should.exist(ancestorNodeEl);
2372
+ should.exist(originalRootNodeEl);
2373
+ originalRootNodeEl.closest('.hierarchy').should.not.equal(ancestorNodeEl.closest('.hierarchy'));
2374
+ });
2375
+
2376
+ it('addDescendants() accepts a native parent element and appends descendant hierarchy nodes', function () {
2377
+ oc.addDescendants([{ id: 'n11', name: 'Li Xin' }], $bomiao);
2378
+
2379
+ should.exist($bomiao.nextElementSibling);
2380
+ matchesSelector($bomiao.nextElementSibling, '.nodes').should.be.true;
2381
+ childElements($bomiao.nextElementSibling, '.hierarchy').should.have.lengthOf(1);
2382
+ queryAll('#n11', $bomiao.nextElementSibling).should.have.lengthOf(1);
2383
+ });
2384
+
2385
+ it('showDropZones() accepts a native dragged node', function () {
2386
+ oc.showDropZones($heihei);
2387
+
2388
+ getState(oc.chart, 'dragged').should.equal($heihei);
2389
+ queryAll('.allowedDrop', oc.chart).length.should.equal(queryAll('.node', oc.chart).length);
2390
+ });
2391
+
2392
+ it('hideDropZones() clears all allowedDrop classes', function () {
2393
+ oc.showDropZones($heihei);
2394
+
2395
+ oc.hideDropZones();
2396
+
2397
+ queryAll('.allowedDrop', oc.chart).should.have.lengthOf(0);
2398
+ });
2399
+
2400
+ it('processExternalDrop() accepts native drop and dragged nodes', function () {
2401
+ const dropSpy = sinon.spy();
2402
+
2403
+ $bomiao.addEventListener('drop', dropSpy);
2404
+
2405
+ oc.processExternalDrop($bomiao, $heihei);
2406
+
2407
+ getState(oc.chart, 'dragged').should.equal($heihei);
2408
+ dropSpy.should.have.been.calledOnce;
2409
+ dropSpy.firstCall.args[0].type.should.equal('drop');
2410
+ });
2411
+
2412
+ it('exportPNG() creates and reuses a native download anchor', function () {
2413
+ const originalNavigator = window.navigator;
2414
+ const originalClick = window.HTMLAnchorElement.prototype.click;
2415
+ const originalWebkitAppearance = document.documentElement.style.WebkitAppearance;
2416
+ const clickSpy = sinon.spy();
2417
+ const fakeCanvas = {
2418
+ toDataURL: function () {
2419
+ return 'data:image/png;base64,fake';
2420
+ }
2421
+ };
2422
+
2423
+ Object.defineProperty(window, 'navigator', {
2424
+ configurable: true,
2425
+ value: {
2426
+ appName: 'Netscape',
2427
+ appVersion: 'Chrome'
2428
+ }
2429
+ });
2430
+ document.documentElement.style.WebkitAppearance = 'none';
2431
+ window.HTMLAnchorElement.prototype.click = clickSpy;
2432
+
2433
+ oc.exportPNG(fakeCanvas, 'UnitExportPng');
2434
+ oc.exportPNG(fakeCanvas, 'UnitExportPng2');
2435
+
2436
+ queryAll('.download-btn', $container).should.have.lengthOf(1);
2437
+ query('.download-btn', $container).getAttribute('download').should.equal('UnitExportPng2.png');
2438
+ query('.download-btn', $container).getAttribute('href').should.equal('data:image/png;base64,fake');
2439
+ clickSpy.callCount.should.equal(2);
2440
+
2441
+ Object.defineProperty(window, 'navigator', {
2442
+ configurable: true,
2443
+ value: originalNavigator
2444
+ });
2445
+ document.documentElement.style.WebkitAppearance = originalWebkitAppearance;
2446
+ window.HTMLAnchorElement.prototype.click = originalClick;
2447
+ });
2448
+
2449
+ it('export() passes the visible chart element to html2canvas and exports png by default', function (done) {
2450
+ const originalHtml2canvas = global.html2canvas;
2451
+ const originalWindowHtml2canvas = window.html2canvas;
2452
+ const exportPNGStub = sinon.stub(oc, 'exportPNG');
2453
+ const fakeCanvas = {
2454
+ toDataURL: function () {
2455
+ return 'data:image/png;base64,fake';
2456
+ }
2457
+ };
2458
+
2459
+ global.html2canvas = function (element, options) {
2460
+ try {
2461
+ element.should.equal(oc.chart);
2462
+ options.width.should.equal(oc.chart.clientWidth);
2463
+ options.height.should.equal(oc.chart.clientHeight);
2464
+ options.scale.should.equal(2);
2465
+ options.useCORS.should.equal(true);
2466
+ } catch (error) {
2467
+ done(error);
2468
+ return {
2469
+ then: function () {}
2470
+ };
2471
+ }
2472
+
2473
+ return {
2474
+ then: function (resolve) {
2475
+ resolve(fakeCanvas);
2476
+ return {
2477
+ then: function () {}
2478
+ };
2479
+ }
2480
+ };
2481
+ };
2482
+
2483
+ window.html2canvas = global.html2canvas;
2484
+
2485
+ oc.export('UnitExport');
2486
+
2487
+ setTimeout(function () {
2488
+ try {
2489
+ exportPNGStub.should.have.been.calledOnce;
2490
+ exportPNGStub.firstCall.args[0].should.equal(fakeCanvas);
2491
+ exportPNGStub.firstCall.args[1].should.equal('UnitExport');
2492
+ $container.classList.contains('canvasContainer').should.be.false;
2493
+ query('.mask', $container).classList.contains('hidden').should.be.true;
2494
+ global.html2canvas = originalHtml2canvas;
2495
+ window.html2canvas = originalWindowHtml2canvas;
2496
+ exportPNGStub.restore();
2497
+ done();
2498
+ } catch (error) {
2499
+ global.html2canvas = originalHtml2canvas;
2500
+ window.html2canvas = originalWindowHtml2canvas;
2501
+ exportPNGStub.restore();
2502
+ done(error);
2503
+ }
2504
+ }, 0);
2505
+ });
2506
+
2507
+ it('export() returns false when the chart container already has a spinner', function () {
2508
+ const html2canvasStub = sinon.stub(global, 'html2canvas');
2509
+ const spinnerEl = document.createElement('i');
2510
+
2511
+ spinnerEl.className = 'spinner';
2512
+ $container.appendChild(spinnerEl);
2513
+
2514
+ oc.export('BlockedExport').should.equal(false);
2515
+ html2canvasStub.should.not.have.been.called;
2516
+
2517
+ spinnerEl.remove();
2518
+ html2canvasStub.restore();
2519
+ });
2520
+
2521
+ it('export() routes pdf exports to exportPDF()', function (done) {
2522
+ const originalHtml2canvas = global.html2canvas;
2523
+ const originalWindowHtml2canvas = window.html2canvas;
2524
+ const exportPDFStub = sinon.stub(oc, 'exportPDF');
2525
+ const fakeCanvas = {
2526
+ toDataURL: function () {
2527
+ return 'data:image/png;base64,fake';
2528
+ }
2529
+ };
2530
+
2531
+ global.html2canvas = function () {
2532
+ return {
2533
+ then: function (resolve) {
2534
+ resolve(fakeCanvas);
2535
+ return {
2536
+ then: function () {}
2537
+ };
2538
+ }
2539
+ };
2540
+ };
2541
+
2542
+ window.html2canvas = global.html2canvas;
2543
+
2544
+ oc.export('UnitExportPdf', 'pdf');
2545
+
2546
+ setTimeout(function () {
2547
+ try {
2548
+ exportPDFStub.should.have.been.calledOnce;
2549
+ exportPDFStub.firstCall.args[0].should.equal(fakeCanvas);
2550
+ exportPDFStub.firstCall.args[1].should.equal('UnitExportPdf');
2551
+ global.html2canvas = originalHtml2canvas;
2552
+ window.html2canvas = originalWindowHtml2canvas;
2553
+ exportPDFStub.restore();
2554
+ done();
2555
+ } catch (error) {
2556
+ global.html2canvas = originalHtml2canvas;
2557
+ window.html2canvas = originalWindowHtml2canvas;
2558
+ exportPDFStub.restore();
2559
+ done(error);
2560
+ }
2561
+ }, 0);
2562
+ });
2563
+ });