]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/test/specs/tree/Panel.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / test / specs / tree / Panel.js
1 describe("Ext.tree.Panel", function(){
2
3 var TreeItem = Ext.define(null, {
4 extend: 'Ext.data.TreeModel',
5 fields: ['id', 'text', 'secondaryId'],
6 proxy: {
7 type: 'memory'
8 }
9 }),
10 tree, view, makeTree, testNodes, store, rootNode,
11 synchronousLoad = true,
12 treeStoreLoad = Ext.data.TreeStore.prototype.load,
13 loadStore;
14
15 function spyOnEvent(object, eventName, fn) {
16 var obj = {
17 fn: fn || Ext.emptyFn
18 },
19 spy = spyOn(obj, "fn");
20 object.addListener(eventName, obj.fn);
21 return spy;
22 }
23
24 beforeEach(function() {
25 // Override so that we can control asynchronous loading
26 loadStore = Ext.data.TreeStore.prototype.load = function() {
27 treeStoreLoad.apply(this, arguments);
28 if (synchronousLoad) {
29 this.flushLoad.apply(this, arguments);
30 }
31 return this;
32 };
33
34 MockAjaxManager.addMethods();
35 testNodes = [{
36 id: 'A',
37 text: 'A',
38 secondaryId: 'AA',
39 children: [{
40 id: 'B',
41 text: 'B',
42 secondaryId: 'BB',
43 children: [{
44 id: 'C',
45 text: 'C',
46 secondaryId: 'C',
47 leaf: true
48 }, {
49 id: 'D',
50 text: 'D',
51 secondaryId: 'D',
52 leaf: true
53 }]
54 }, {
55 id: 'E',
56 text: 'E',
57 secondaryId: 'EE',
58 leaf: true
59 }, {
60 id: 'F',
61 text: 'F',
62 secondaryId: 'FF',
63 children: [{
64 id: 'G',
65 text: 'G',
66 secondaryId: 'GG',
67 children: [{
68 id: 'H',
69 text: 'H',
70 secondaryId: 'HH',
71 leaf: true
72 }]
73 }]
74 }]
75 }, {
76 id: 'I',
77 text: 'I',
78 secondaryId: 'II',
79 children: [{
80 id: 'J',
81 text: 'J',
82 secondaryId: 'JJ',
83 children: [{
84 id: 'K',
85 text: 'K',
86 secondaryId: 'KK',
87 leaf: true
88 }]
89 }, {
90 id: 'L',
91 text: 'L',
92 secondaryId: 'LL',
93 leaf: true
94 }]
95 }, {
96 id: 'M',
97 text: 'M',
98 secondaryId: 'MM',
99 children: [{
100 id: 'N',
101 text: 'N',
102 secondaryId: 'NN',
103 leaf: true
104 }]
105 }];
106
107 makeTree = function(nodes, cfg, storeCfg, rootCfg) {
108 cfg = cfg || {};
109 Ext.applyIf(cfg, {
110 animate: false,
111 renderTo: Ext.getBody(),
112 viewConfig: {
113 loadMask: false
114 },
115 store: store = new Ext.data.TreeStore(Ext.apply({
116 model: TreeItem,
117 root: Ext.apply({
118 secondaryId: 'root',
119 id: 'root',
120 text: 'Root',
121 children: nodes
122 }, rootCfg)
123 }, storeCfg))
124 });
125 tree = new Ext.tree.Panel(cfg);
126 view = tree.view;
127 rootNode = tree.getRootNode();
128 };
129 });
130
131 afterEach(function(){
132 // Undo the overrides.
133 Ext.data.TreeStore.prototype.load = treeStoreLoad;
134
135 Ext.destroy(tree);
136 tree = makeTree = null;
137 MockAjaxManager.removeMethods();
138 });
139
140 describe('Checkbox tree nodes', function() {
141 var eventRec,
142 record,
143 row,
144 checkbox;
145
146 beforeEach(function() {
147 eventRec = null;
148 makeTree(testNodes, {
149 listeners: {
150 checkchange: function(rec) {
151 eventRec = rec;
152 }
153 }
154 });
155 store.getRoot().cascadeBy(function(r) {
156 r.set('checked', false);
157 });
158 tree.expandAll();
159 record = store.getAt(1);
160 row = Ext.get(view.getRow(record));
161 checkbox = row.down(view.checkboxSelector, true);
162 });
163
164 it('should fire the checkchange event', function() {
165 jasmine.fireMouseEvent(checkbox, 'click');
166 expect(eventRec).toBe(record);
167 expect(record.get('checked')).toBe(true);
168 });
169 it('should veto checkchange if false is returned from a beforecheckchange handler', function() {
170 tree.on({
171 beforecheckchange: function(rec) {
172 eventRec = rec;
173 return false;
174 }
175 });
176 jasmine.fireMouseEvent(checkbox, 'click');
177 expect(eventRec).toBe(record);
178 expect(record.get('checked')).toBe(false);
179 });
180 });
181
182 // https://sencha.jira.com/browse/EXTJS-16367
183 describe("record with a cls field", function() {
184 it("should set the cls on the TD element", function() {
185 makeTree(testNodes);
186 var createRowSpy = spyOn(view, 'createRowElement').andCallThrough();
187
188 rootNode.childNodes[0].set('cls', 'foobar');
189 rootNode.expand();
190 expect(view.all.item(1).down('td').hasCls('foobar')).toBe(true);
191
192 // The cls is applied to the TD, so the row will have to be created. Cannot use in-cell updating
193 rootNode.childNodes[0].set('cls', 'bletch');
194 expect(createRowSpy).toHaveBeenCalled();
195 expect(view.all.item(1).down('td').hasCls('foobar')).toBe(false);
196 expect(view.all.item(1).down('td').hasCls('bletch')).toBe(true);
197 });
198 });
199
200 describe("construction", function() {
201 it("should render while the root node is loading", function() {
202 expect(function() {
203 makeTree(null, null, {
204 proxy: {
205 type: 'ajax',
206 url: 'fake'
207 }
208 }, {
209 expanded: true
210 });
211 }).not.toThrow();
212 });
213 });
214
215 describe("setting the root node", function() {
216 it("should set the nodes correctly when setting root on the store", function() {
217 makeTree();
218 store.setRootNode({
219 expanded: true,
220 children: testNodes
221 });
222 expect(store.getCount()).toBe(4);
223 expect(store.getAt(0).id).toBe('root');
224 expect(store.getAt(1).id).toBe('A');
225 expect(store.getAt(2).id).toBe('I');
226 expect(store.getAt(3).id).toBe('M');
227 });
228
229 it("should set the nodes correctly when setting root on the tree", function() {
230 makeTree();
231 tree.setRootNode({
232 expanded: true,
233 children: testNodes
234 });
235 expect(store.getCount()).toBe(4);
236 expect(store.getAt(0).id).toBe('root');
237 expect(store.getAt(1).id).toBe('A');
238 expect(store.getAt(2).id).toBe('I');
239 expect(store.getAt(3).id).toBe('M');
240 });
241
242 it("should preserve events", function() {
243 var spy = jasmine.createSpy();
244 var root2 = {
245 expanded: true,
246 children: testNodes
247 };
248 makeTree();
249 tree.on({
250 beforeitemcollapse: spy,
251 beforeitemexpand: spy,
252 itemcollapse: spy,
253 itemexpand: spy
254 });
255 tree.setRootNode(root2);
256
257 rootNode = tree.getRootNode();
258 rootNode.childNodes[0].expand();
259 rootNode.childNodes[0].collapse();
260
261 expect(spy.callCount).toBe(4);
262 });
263 });
264
265 describe('Binding to a TreeStore', function() {
266 it('should bind to a TreeStore in the ViewModel', function() {
267 tree = new Ext.panel.Panel({
268 renderTo: document.body,
269 height: 400,
270 width: 600,
271 layout: 'fit',
272 viewModel: {
273 stores: {
274 nodes: {
275 type: 'tree',
276 model: TreeItem,
277 root: {
278 secondaryId: 'root',
279 id: 'root',
280 text: 'Root',
281 children: testNodes,
282 expanded: true
283 }
284 }
285 }
286 },
287 items: {
288 xtype: 'treepanel',
289 bind: {
290 store: '{nodes}'
291 }
292 }
293 });
294 var treepanel = tree.down('treepanel');
295
296 // The provided default root node has no children
297 expect(treepanel.getRootNode().childNodes.length).toBe(0);
298
299 // Wait until the "nodes" store has been bound
300 waitsFor(function() {
301 return treepanel.getRootNode().childNodes.length === 3 && treepanel.getView().all.getCount() === 4;
302 }, 'new store to be bound to');
303 });
304 });
305
306 describe("mouse click to expand/collapse", function() {
307 function makeAutoTree(animate, data) {
308 makeTree(data, {
309 animate: animate
310 }, null, {
311 expanded: true
312 });
313 }
314
315 describe("Clicking on expander", function() {
316 it("should not fire a click event on click of expnder", function() {
317 makeAutoTree(true, [{
318 id: 'a',
319 expanded: false,
320 children: [{
321 id: 'b'
322 }]
323 }]);
324 var spy = jasmine.createSpy(),
325 cellClickSpy = jasmine.createSpy(),
326 itemClickSpy = jasmine.createSpy(),
327 height = tree.getHeight(),
328 expander = view.getCell(1, 0).down(view.expanderSelector),
329 cell10 = new Ext.grid.CellContext(view).setPosition(1, 0);
330
331 // Focus must be on the tree cell upon expand
332 tree.on('expand', function() {
333 expect(Ext.Element.getActiveElement).toBe(cell10.getCell(true));
334 });
335 tree.on('afteritemexpand', spy);
336 tree.on('cellclick', cellClickSpy);
337 tree.on('itemclick', itemClickSpy);
338 jasmine.fireMouseEvent(expander, 'click');
339 waitsFor(function() {
340 return spy.callCount > 0;
341 });
342 runs(function() {
343 expect(tree.getHeight()).toBeGreaterThan(height);
344
345 // Clicking on an expander should not trigger a cell click
346 expect(cellClickSpy).not.toHaveBeenCalled();
347
348 // Clicking on an expander should not trigger an item click
349 expect(itemClickSpy).not.toHaveBeenCalled();
350 });
351 });
352 });
353
354 });
355
356 describe("auto height with expand/collapse", function() {
357 function makeAutoTree(animate, data) {
358 makeTree(data, {
359 animate: animate
360 }, null, {
361 expanded: true
362 });
363 }
364
365 describe("with animate: true", function() {
366 it("should update the height after an expand animation", function() {
367 makeAutoTree(true, [{
368 id: 'a',
369 expanded: false,
370 children: [{
371 id: 'b'
372 }]
373 }]);
374 var spy = jasmine.createSpy(),
375 height = tree.getHeight();
376
377 tree.on('afteritemexpand', spy);
378 tree.getRootNode().firstChild.expand();
379 waitsFor(function() {
380 return spy.callCount > 0;
381 });
382 runs(function() {
383 expect(tree.getHeight()).toBeGreaterThan(height);
384 });
385 });
386
387 it("should update the height after a collapse animation", function() {
388 makeAutoTree(true, [{
389 id: 'a',
390 expanded: true,
391 children: [{
392 id: 'b'
393 }]
394 }]);
395 var spy = jasmine.createSpy(),
396 height = tree.getHeight();
397
398 tree.on('afteritemcollapse', spy);
399 tree.getRootNode().firstChild.collapse();
400 waitsFor(function() {
401 return spy.callCount > 0;
402 });
403 runs(function() {
404 expect(tree.getHeight()).toBeLessThan(height);
405 });
406 });
407 });
408
409 describe("with animate: false", function() {
410 it("should update the height after an expand animation", function() {
411 makeAutoTree(false, [{
412 id: 'a',
413 expanded: false,
414 children: [{
415 id: 'b'
416 }]
417 }]);
418
419 var height = tree.getHeight();
420 tree.getRootNode().firstChild.expand();
421 expect(tree.getHeight()).toBeGreaterThan(height);
422 });
423
424 it("should update the height after a collapse animation", function() {
425 makeAutoTree(false, [{
426 id: 'a',
427 expanded: true,
428 children: [{
429 id: 'b'
430 }]
431 }]);
432
433 var height = tree.getHeight();
434 tree.getRootNode().firstChild.collapse();
435 expect(tree.getHeight()).toBeLessThan(height);
436 });
437 });
438 });
439
440 describe('collapsing when collapse zone overflows the rendered zone', function() {
441 beforeEach(function() {
442 for (var i = 0; i < 100; i++) {
443 testNodes[0].children.push({
444 text: 'Extra node ' + i,
445 id: 'extra-node-' + i
446 });
447 }
448 testNodes[0].expanded = true;
449
450 makeTree(testNodes, {
451 renderTo: document.body,
452 height: 200,
453 width: 400
454 }, null, {
455 expanded: true
456 });
457 });
458
459 it("should collapse correctly, leaving the collapsee's siblings visible", function() {
460 // Collapse node "A".
461 tree.getRootNode().childNodes[0].collapse();
462
463 // We now should have "Root", and nodes "A", "I" and "M"
464 // https://sencha.jira.com/browse/EXTJS-13908
465 expect(tree.getView().all.getCount()).toBe(4);
466 });
467 });
468
469 describe("sortchange", function() {
470 it("should only fire a single sortchange event", function() {
471 var spy = jasmine.createSpy();
472 makeTree(testNodes, {
473 columns: [{
474 xtype: 'treecolumn',
475 dataIndex: 'text'
476 }]
477 });
478 tree.on('sortchange', spy);
479 // Pass the position so we don't click right on the edge (trigger a resize)
480 jasmine.fireMouseEvent(tree.down('treecolumn').titleEl.dom, 'click', 20, 10);
481 expect(spy).toHaveBeenCalled();
482 expect(spy.callCount).toBe(1);
483 });
484 });
485
486 describe('reconfigure', function() {
487 beforeEach(function() {
488 makeTree(testNodes, {
489 rootVisible: false,
490 singleExpand: true,
491 height: 200
492 }, null, {
493 expanded: true
494 });
495 });
496 it('should preserve singleExpand:true', function() {
497 // Expand childNodes[0]
498 rootNode.childNodes[0].expand();
499 expect(rootNode.childNodes[0].isExpanded()).toBe(true);
500
501 // This must collapse childNodes[0] while expanding childNodes[1] because of singleExpand
502 rootNode.childNodes[1].expand();
503 expect(rootNode.childNodes[0].isExpanded()).toBe(false);
504 expect(rootNode.childNodes[1].isExpanded()).toBe(true);
505
506 // Three root's childNodes plus the two child nodes of childNode[1]
507 expect(store.getCount()).toBe(5);
508
509 // Identical Store to reconfigure with
510 var newStore = new Ext.data.TreeStore({
511 model: TreeItem,
512 root: {
513 secondaryId: 'root',
514 id: 'root',
515 text: 'Root',
516 children: testNodes,
517 expanded: true
518 }
519 });
520
521 tree.reconfigure(newStore);
522 rootNode = newStore.getRootNode();
523
524 // Back down to just the three root childNodes.
525 expect(newStore.getCount()).toBe(3);
526
527 // Expand childNodes[0]
528 rootNode.childNodes[0].expand();
529 expect(rootNode.childNodes[0].isExpanded()).toBe(true);
530
531 // This must collapse childNodes[0] while expanding childNodes[1] because of singleExpand
532 rootNode.childNodes[1].expand();
533 expect(rootNode.childNodes[0].isExpanded()).toBe(false);
534 expect(rootNode.childNodes[1].isExpanded()).toBe(true);
535
536 // Three root's childNodes plus the two child nodes of childNode[1]
537 expect(newStore.getCount()).toBe(5);
538 });
539 });
540
541 describe('autoexpand collapsed ancestors', function() {
542 beforeEach(function() {
543 makeTree(testNodes, {
544 height: 250
545 });
546 });
547 it("should expand the whole path down to 'G' as well as 'G'", function() {
548 // Start off with only the root visible.
549 expect(store.getCount()).toBe(1);
550
551 tree.getStore().getNodeById('G').expand();
552
553 // "A" should be expanded all the way down to "H", then "I", then "M"
554 expect(store.getCount()).toBe(9);
555 });
556 });
557
558 describe("removeAll", function() {
559 beforeEach(function(){
560 makeTree(testNodes, {
561 height: 100
562 });
563 });
564 it("should only refresh once when removeAll called", function() {
565 var nodeA = tree.getStore().getNodeById('A'),
566 buffered;
567
568 expect(tree.view.refreshCounter).toBe(1);
569 tree.expandAll();
570 buffered = view.bufferedRenderer && view.all.getCount >= view.bufferedRenderer.viewSize;
571
572 // With all the nodes fully preloaded, a recursive expand
573 // should do one refresh.
574 expect(view.refreshCounter).toBe(2);
575
576 // The bulkremove event fired by NodeInterface.removeAll should trigger the NodeStore call onNodeCollapse.
577 // In response, the NodeStore removes all child nodes, and fired bulkremove. The BufferedRendererTreeView
578 // override processes the removal without calling view's refresh.
579 // Refresh will only be called if buffered rendering has been *used*, ie if the number of rows has reached
580 // the buffered renderer's view size. If not, a regular non-buffered type update will handle the remove
581 // and the refresh count will still be 2.
582 nodeA.removeAll();
583 expect(view.refreshCounter).toBe(buffered ? 3 : 2);
584 });
585 });
586
587 describe("Getting owner tree", function() {
588 beforeEach(function(){
589 makeTree(testNodes);
590 });
591 it("should find the owner tree", function() {
592 var store = tree.getStore(),
593 h = store.getNodeById('H');
594
595 expect(h.getOwnerTree()).toBe(tree);
596 });
597 });
598
599 describe("updating row attributes", function() {
600 beforeEach(function(){
601 makeTree(testNodes);
602 });
603
604 it("should set the data-qtip attribute", function() {
605 var rootRow = tree.view.getRow(rootNode),
606 rootCls = rootRow.className;
607
608 rootNode.set('qtip', 'Foo');
609
610 // Class should not change
611 expect(rootRow.className).toBe(rootCls);
612
613 // data-qtip must be set
614 expect(rootRow.getAttribute('data-qtip')).toBe('Foo');
615 });
616
617 it("should add the expanded class on expand", function() {
618 var view = tree.getView(),
619 cls = view.expandedCls;
620
621 expect(view.getRow(rootNode)).not.toHaveCls(cls);
622 rootNode.expand();
623 expect(view.getRow(rootNode)).toHaveCls(cls);
624 });
625
626 it("should remove the expanded class on collapse", function() {
627 var view = tree.getView(),
628 cls = view.expandedCls;
629
630 rootNode.expand();
631 expect(view.getRow(rootNode)).toHaveCls(cls);
632 rootNode.collapse();
633 expect(view.getRow(rootNode)).not.toHaveCls(cls);
634 })
635 });
636
637 describe("expandPath/selectPath", function(){
638 describe("expandPath", function(){
639 var expectedSuccess, expectedNode;
640 beforeEach(function() {
641 expectedSuccess = false;
642 makeTree(testNodes);
643 });
644
645 describe("callbacks", function(){
646
647 describe("empty path", function() {
648 it("should fire the callback with success false & a null node", function() {
649 tree.expandPath('', null, null, function(success, node){
650 expectedSuccess = success;
651 expectedNode = node;
652 });
653 expect(expectedSuccess).toBe(false);
654 expect(expectedNode).toBeNull();
655 });
656
657 it("should default the scope to the tree", function(){
658 var scope;
659 tree.expandPath('', null, null, function(){
660 scope = this;
661 });
662 expect(scope).toBe(tree);
663 });
664
665 it("should use any specified scope", function(){
666 var o = {}, scope;
667 tree.expandPath('', null, null, function(){
668 scope = this;
669 }, o);
670 expect(scope).toBe(o);
671 });
672 });
673
674 describe("invalid root", function() {
675 it("should fire the callback with success false & the root", function() {
676 tree.expandPath('/NOTROOT', null, null, function(success, node){
677 expectedSuccess = success;
678 expectedNode = node;
679 });
680 expect(expectedSuccess).toBe(false);
681 expect(expectedNode).toBe(tree.getRootNode());
682 });
683
684 it("should default the scope to the tree", function(){
685 var scope;
686 tree.expandPath('/NOTROOT', null, null, function(){
687 scope = this;
688 });
689 expect(scope).toBe(tree);
690 });
691
692 it("should use any specified scope", function(){
693 var o = {}, scope;
694 tree.expandPath('/NOTROOT', null, null, function(){
695 scope = this;
696 }, o);
697 expect(scope).toBe(o);
698 });
699 });
700
701 describe("fully successful expand", function(){
702 describe('Old API', function() {
703 it("should fire the callback with success true and the last node", function(){
704 tree.expandPath('/root/A/B', null, null, function(success, lastExpanded){
705 expectedSuccess = success;
706 expectedNode = lastExpanded;
707 });
708 expect(expectedSuccess).toBe(true);
709 expect(expectedNode).toBe(tree.getStore().getNodeById('B'));
710 expect(view.all.getCount()).toBe(9);
711 });
712
713 it("should default the scope to the tree", function() {
714 var scope;
715 tree.expandPath('/root/A/B', null, null, function(success, lastExpanded) {
716 scope = this;
717 });
718 expect(scope).toBe(tree);
719 });
720
721 it("should use any specified scope", function(){
722 var o = {}, scope;
723 tree.expandPath('/root/A/B', null, null, function(success, lastExpanded) {
724 scope = this;
725 }, o);
726 expect(scope).toBe(o);
727 });
728
729 it('should be able to start from any existing node', function() {
730 tree.expandPath('G', null, null, function(success, lastExpanded) {
731 expectedSuccess = success;
732 expectedNode = lastExpanded;
733 });
734 expect(expectedSuccess).toBe(true);
735 expect(expectedNode).toBe(store.getNodeById('G'));
736 expect(view.all.getCount()).toBe(9);
737 });
738 });
739 describe('New API', function() {
740 var lastHtmlNode;
741
742 it("should fire the callback with success true and the last node", function(){
743 tree.expandPath('/root/A/B', {
744 callback: function(success, lastExpanded, lastNode) {
745 expectedSuccess = success;
746 expectedNode = lastExpanded;
747 lastHtmlNode = lastNode;
748 },
749 select: true
750 });
751 waitsFor(function() {
752 return expectedSuccess;
753 });
754 runs(function() {
755 expect(expectedNode).toBe(tree.getStore().getNodeById('B'));
756 expect(view.all.getCount()).toBe(9);
757 expect(tree.getSelectionModel().getSelection()[0]).toBe(expectedNode);
758 expect(lastHtmlNode).toBe(view.getNode(tree.getStore().getNodeById('B')));
759 });
760 });
761
762 it("should default the scope to the tree", function() {
763 var scope;
764 tree.expandPath('/root/A/B', {
765 callback: function(success, lastExpanded) {
766 scope = this;
767 }
768 });
769 waitsFor(function() {
770 return scope === tree;
771 });
772 });
773
774 it("should use any specified scope", function(){
775 var o = {}, scope;
776 tree.expandPath('/root/A/B', {
777 callback:
778 function(success, lastExpanded) {
779 scope = this;
780 },
781 scope: o
782 });
783 waitsFor(function() {
784 return scope === o;
785 });
786 });
787
788 it('should be able to start from any existing node', function() {
789 tree.expandPath('G', {
790 callback: function(success , lastExpanded) {
791 expectedSuccess = success;
792 expectedNode = lastExpanded;
793 }
794 });
795 waitsFor(function() {
796 return expectedSuccess;
797 });
798 runs(function() {
799 expect(expectedNode).toBe(store.getNodeById('G'));
800 expect(view.all.getCount()).toBe(9);
801 });
802 });
803 });
804 });
805
806 describe("partial expand", function(){
807 it("should fire the callback with success false and the last successful node", function(){
808 tree.expandPath('/root/A/FAKE', null, null, function(success, node){
809 expectedSuccess = success;
810 expectedNode = node;
811 });
812 expect(expectedSuccess).toBe(false);
813 expect(expectedNode).toBe(tree.getStore().getById('A'));
814 });
815
816 it("should default the scope to the tree", function(){
817 var scope;
818 tree.expandPath('/root/A/FAKE', null, null, function(){
819 scope = this;
820 });
821 expect(scope).toBe(tree);
822 });
823
824 it("should use any specified scope", function(){
825 var o = {}, scope;
826 tree.expandPath('/root/A/FAKE', null, null, function(){
827 scope = this;
828 }, o);
829 expect(scope).toBe(o);
830 });
831 });
832 });
833
834 describe("custom field", function(){
835 it("should default the field to the idProperty", function(){
836 tree.expandPath('/root/M');
837 expect(tree.getStore().getById('M').isExpanded()).toBe(true);
838 });
839
840 it("should accept a custom field from the model", function(){
841 tree.expandPath('/root/AA/FF/GG', 'secondaryId');
842 expect(tree.getStore().getById('G').isExpanded()).toBe(true);
843 });
844 });
845
846 describe("custom separator", function(){
847 it("should default the separator to /", function(){
848 tree.expandPath('/root/A');
849 expect(tree.getStore().getById('A').isExpanded()).toBe(true);
850 });
851
852 it("should accept a custom separator", function(){
853 tree.expandPath('|root|A|B', null, '|');
854 expect(tree.getStore().getById('B').isExpanded()).toBe(true);
855 });
856 });
857
858 describe("various path tests", function(){
859 it("should expand the root node", function(){
860 tree.expandPath('/root');
861 expect(tree.getRootNode().isExpanded()).toBe(true);
862 });
863
864 it("should fire success if the ending node is a leaf", function(){
865 tree.expandPath('/root/I/L', null, null, function(success, node){
866 expectedSuccess = success;
867 expectedNode = node;
868 });
869 expect(expectedSuccess).toBe(true);
870 expect(expectedNode).toBe(tree.getStore().getById('L'));
871 });
872 });
873
874 });
875
876 describe("selectPath", function(){
877 var isSelected = function(id){
878 var node = tree.getStore().getById(id);
879 return tree.getSelectionModel().isSelected(node);
880 };
881
882 var expectedSuccess, expectedNode;
883 beforeEach(function() {
884 expectedSuccess = false;
885 makeTree(testNodes);
886 });
887
888 describe("callbacks", function(){
889
890 describe("empty path", function() {
891 it("should fire the callback with success false & a null node", function() {
892 var expectedSuccess, expectedNode;
893 tree.selectPath('', null, null, function(success, node){
894 expectedSuccess = success;
895 expectedNode = node;
896 });
897 expect(expectedSuccess).toBe(false);
898 expect(expectedNode).toBeNull();
899 });
900
901 it("should default the scope to the tree", function(){
902 var scope;
903 tree.selectPath('', null, null, function(){
904 scope = this;
905 });
906 expect(scope).toBe(tree);
907 });
908
909 it("should use any specified scope", function(){
910 var o = {}, scope;
911 tree.selectPath('', null, null, function(){
912 scope = this;
913 }, o);
914 expect(scope).toBe(o);
915 });
916 });
917
918 describe("root", function() {
919 it("should fire the callback with success true & the root", function() {
920 var expectedSuccess, expectedNode;
921 tree.selectPath('/root', null, null, function(success, node){
922 expectedSuccess = success;
923 expectedNode = node;
924 });
925 expect(expectedSuccess).toBe(true);
926 expect(expectedNode).toBe(tree.getRootNode());
927 });
928
929 it("should default the scope to the tree", function(){
930 var scope;
931 tree.selectPath('/root', null, null, function(){
932 scope = this;
933 });
934 expect(scope).toBe(tree);
935 });
936
937 it("should use any specified scope", function(){
938 var o = {}, scope;
939 tree.selectPath('/root', null, null, function(){
940 scope = this;
941 }, o);
942 expect(scope).toBe(o);
943 });
944 });
945
946 describe("fully successful expand", function(){
947 it("should fire the callback with success true and the last node", function(){
948 var expectedSuccess, expectedNode;
949 tree.selectPath('/root/A/B', null, null, function(success, node){
950 expectedSuccess = success;
951 expectedNode = node;
952 });
953 expect(expectedSuccess).toBe(true);
954 expect(expectedNode).toBe(tree.getStore().getById('B'));
955 });
956
957 it("should default the scope to the tree", function(){
958 var scope;
959 tree.selectPath('/root/A/B', null, null, function(){
960 scope = this;
961 });
962 expect(scope).toBe(tree);
963 });
964
965 it("should use any specified scope", function(){
966 var o = {}, scope;
967 tree.selectPath('/root/A/B', null, null, function(){
968 scope = this;
969 }, o);
970 expect(scope).toBe(o);
971 });
972 });
973
974 describe("partial expand", function(){
975 it("should fire the callback with success false and the last successful node", function(){
976 var expectedSuccess, expectedNode;
977 tree.selectPath('/root/A/FAKE', null, null, function(success, node){
978 expectedSuccess = success;
979 expectedNode = node;
980 });
981 expect(expectedSuccess).toBe(false);
982 expect(expectedNode).toBe(tree.getStore().getById('A'));
983 });
984
985 it("should default the scope to the tree", function(){
986 var scope;
987 tree.selectPath('/root/A/FAKE', null, null, function(){
988 scope = this;
989 });
990 expect(scope).toBe(tree);
991 });
992
993 it("should use any specified scope", function(){
994 var o = {}, scope;
995 tree.selectPath('/root/A/FAKE', null, null, function(){
996 scope = this;
997 }, o);
998 expect(scope).toBe(o);
999 });
1000 });
1001 });
1002
1003 describe("custom field", function(){
1004 it("should default the field to the idProperty", function(){
1005 tree.selectPath('/root/M');
1006 expect(isSelected('M')).toBe(true);
1007 });
1008
1009 it("should accept a custom field from the model", function(){
1010 tree.selectPath('/root/AA/FF/GG', 'secondaryId');
1011 expect(isSelected('G')).toBe(true);
1012 });
1013 });
1014
1015 describe("custom separator", function(){
1016 it("should default the separator to /", function(){
1017 tree.selectPath('/root/A');
1018 expect(isSelected('A')).toBe(true);
1019 });
1020
1021 it("should accept a custom separator", function(){
1022 tree.selectPath('|root|A|B', null, '|');
1023 expect(isSelected('B')).toBe(true);
1024 });
1025 });
1026
1027 describe("various paths", function(){
1028 it("should be able to select the root", function(){
1029 tree.selectPath('/root');
1030 expect(isSelected('root')).toBe(true);
1031 });
1032
1033 it("should select a leaf node", function(){
1034 tree.selectPath('/root/I/L');
1035 expect(isSelected('L')).toBe(true);
1036 });
1037
1038 it("should not select a node if the full path isn't resolved", function(){
1039 tree.selectPath('/root/I/FAKE');
1040 expect(tree.getSelectionModel().getSelection().length).toBe(0);
1041 });
1042 });
1043 });
1044
1045 describe("special cases", function() {
1046 it("should be able to select a path where the values are numeric", function() {
1047 var NumericModel = Ext.define(null, {
1048 extend: 'Ext.data.TreeModel',
1049 fields: [{name: 'id', type: 'int'}]
1050 });
1051
1052 makeTree([{
1053 id: 1,
1054 text: 'A'
1055 }, {
1056 id: 2,
1057 text: 'B',
1058 children: [{
1059 id: 3,
1060 text: 'B1',
1061 children: [{
1062 id: 4,
1063 text: 'B1_1'
1064 }]
1065 }, {
1066 id: 5,
1067 text: 'B2',
1068 children: [{
1069 id: 6,
1070 text: 'B2_1'
1071 }]
1072 }]
1073 }], null, null, {
1074 id: -1
1075 });
1076
1077 tree.selectPath('2/3/4');
1078 expect(tree.getSelectionModel().isSelected(store.getNodeById(4)));
1079 });
1080
1081 it("should be able to select a path when subclassing Ext.tree.Panel", function() {
1082 var Cls = Ext.define(null, {
1083 extend: 'Ext.tree.Panel',
1084 animate: false,
1085 viewConfig: {
1086 loadMask: false
1087 }
1088 });
1089
1090 tree = new Cls({
1091 renderTo: Ext.getBody(),
1092 store: store = new Ext.data.TreeStore({
1093 model: TreeItem,
1094 root: {
1095 secondaryId: 'root',
1096 id: 'root',
1097 text: 'Root',
1098 children: testNodes
1099 }
1100 })
1101 });
1102 tree.selectPath('/root/A/B/C');
1103 expect(tree.getSelectionModel().isSelected(store.getNodeById('C')));
1104
1105 });
1106 });
1107
1108 });
1109
1110 describe("expand/collapse", function(){
1111 var startingLayoutCounter;
1112
1113 beforeEach(function(){
1114 makeTree(testNodes);
1115 startingLayoutCounter = tree.layoutCounter;
1116 });
1117
1118 describe("expandAll", function(){
1119
1120 describe("callbacks", function(){
1121 it("should pass the direct child nodes of the root", function(){
1122 var expectedNodes,
1123 callCount = 0,
1124 store = tree.getStore();
1125
1126 tree.expandAll(function(nodes) {
1127 expectedNodes = nodes;
1128 callCount++;
1129 });
1130
1131 expect(callCount).toEqual(1);
1132 expect(expectedNodes[0]).toBe(store.getById('A'));
1133 expect(expectedNodes[1]).toBe(store.getById('I'));
1134 expect(expectedNodes[2]).toBe(store.getById('M'));
1135
1136 // Only one layout should have taken place
1137 expect(tree.layoutCounter).toBe(startingLayoutCounter + 1);
1138 });
1139
1140 it("should default the scope to the tree", function() {
1141 var expectedScope;
1142 tree.expandAll(function(){
1143 expectedScope = this;
1144 });
1145 expect(expectedScope).toBe(tree);
1146 });
1147
1148 it("should use a passed scope", function() {
1149 var o = {}, expectedScope;
1150 tree.expandAll(function(){
1151 expectedScope = this;
1152 }, o);
1153 expect(expectedScope).toBe(o);
1154 });
1155 });
1156
1157 it("should expand all nodes", function(){
1158 tree.expandAll();
1159 Ext.Array.forEach(tree.store.getRange(), function(node){
1160 if (!node.isLeaf()) {
1161 expect(node.isExpanded()).toBe(true);
1162 }
1163 });
1164 });
1165
1166 it("should continue down the tree even if some nodes are expanded", function(){
1167 var store = tree.getStore();
1168 store.getNodeById('A').expand();
1169 store.getNodeById('I').expand();
1170 tree.expandAll();
1171 Ext.Array.forEach(tree.store.getRange(), function(node){
1172 if (!node.isLeaf()) {
1173 expect(node.isExpanded()).toBe(true);
1174 }
1175 });
1176 });
1177
1178 });
1179
1180 describe("collapseAll", function(){
1181 describe("callbacks", function(){
1182
1183 it("should pass the direct child nodes of the root", function(){
1184 var expectedNodes,
1185 store = tree.getStore();
1186
1187 tree.collapseAll(function(nodes) {
1188 expectedNodes = nodes;
1189 });
1190
1191 expect(expectedNodes[0]).toBe(store.getNodeById('A'));
1192 expect(expectedNodes[1]).toBe(store.getNodeById('I'));
1193 expect(expectedNodes[2]).toBe(store.getNodeById('M'));
1194 });
1195
1196 it("should default the scope to the tree", function() {
1197 var expectedScope;
1198 tree.collapseAll(function(){
1199 expectedScope = this;
1200 });
1201 expect(expectedScope).toBe(tree);
1202 });
1203
1204 it("should use a passed scope", function() {
1205 var o = {}, expectedScope;
1206 tree.expandAll(function(){
1207 expectedScope = this;
1208 }, o);
1209 expect(expectedScope).toBe(o);
1210 });
1211 });
1212
1213 it("should collapse all nodes", function(){
1214 tree.expandAll();
1215 tree.collapseAll();
1216 Ext.Array.forEach(tree.store.getRange(), function(node){
1217 if (!node.isLeaf()) {
1218 expect(node.isExpanded()).toBe(false);
1219 }
1220 });
1221 });
1222
1223 it("should collapse all nodes all the way down the tree", function(){
1224 tree.expandPath('/root/A/B/C');
1225 tree.getRootNode().collapse();
1226 tree.collapseAll();
1227 Ext.Array.forEach(tree.store.getRange(), function(node){
1228 if (!node.isLeaf()) {
1229 expect(node.isExpanded()).toBe(false);
1230 }
1231 });
1232 });
1233 });
1234
1235 describe("expand", function(){
1236 describe("callbacks", function(){
1237 it("should pass the nodes directly under the expanded node", function(){
1238 var expectedNodes,
1239 store = tree.getStore();
1240
1241 tree.expandNode(tree.getRootNode(), false, function(nodes){
1242 expectedNodes = nodes;
1243 });
1244
1245 expect(expectedNodes[0]).toBe(store.getNodeById('A'));
1246 expect(expectedNodes[1]).toBe(store.getNodeById('I'));
1247 expect(expectedNodes[2]).toBe(store.getNodeById('M'));
1248 });
1249
1250 it("should default the scope to the tree", function(){
1251 var expectedScope;
1252 tree.expandNode(tree.getRootNode(), false, function(){
1253 expectedScope = this;
1254 });
1255 expect(expectedScope).toBe(tree);
1256 });
1257
1258 it("should use a passed scope", function(){
1259 var o = {}, expectedScope;
1260 tree.expandNode(tree.getRootNode(), false, function(){
1261 expectedScope = this;
1262 }, o);
1263 expect(expectedScope).toBe(o);
1264 });
1265 });
1266
1267 describe("deep", function(){
1268 it("should only expand a single level if deep is not specified", function(){
1269 var store = tree.getStore();
1270 tree.expandNode(tree.getRootNode());
1271 expect(store.getNodeById('A').isExpanded()).toBe(false);
1272 expect(store.getNodeById('I').isExpanded()).toBe(false);
1273 expect(store.getNodeById('M').isExpanded()).toBe(false);
1274 });
1275
1276 it("should expand all nodes underneath the expanded node if deep is set", function(){
1277 var store = tree.getStore();
1278 tree.expandPath('/root/A');
1279 tree.expandNode(store.getNodeById('A'), true);
1280 expect(store.getNodeById('B').isExpanded()).toBe(true);
1281 expect(store.getNodeById('F').isExpanded()).toBe(true);
1282 expect(store.getNodeById('G').isExpanded()).toBe(true);
1283 });
1284 });
1285 });
1286
1287 describe("collapse", function(){
1288 describe("callbacks", function(){
1289 it("should pass the nodes directly under the expanded node", function(){
1290 var expectedNodes,
1291 store = tree.getStore();
1292
1293 tree.collapseNode(tree.getRootNode(), false, function(nodes){
1294 expectedNodes = nodes;
1295 });
1296 expect(expectedNodes[0]).toBe(store.getNodeById('A'));
1297 expect(expectedNodes[1]).toBe(store.getNodeById('I'));
1298 expect(expectedNodes[2]).toBe(store.getNodeById('M'));
1299 });
1300
1301 it("should default the scope to the tree", function(){
1302 var expectedScope;
1303 tree.collapseNode(tree.getRootNode(), false, function(){
1304 expectedScope = this;
1305 });
1306 expect(expectedScope).toBe(tree);
1307 });
1308
1309 it("should use a passed scope", function(){
1310 var o = {}, expectedScope;
1311 tree.collapseNode(tree.getRootNode(), false, function(){
1312 expectedScope = this;
1313 }, o);
1314 expect(expectedScope).toBe(o);
1315 });
1316 });
1317
1318 describe("deep", function(){
1319 it("should only collapse a single level if deep is not specified", function(){
1320 var store = tree.getStore();
1321 tree.expandAll();
1322 tree.collapseNode(tree.getRootNode());
1323 expect(store.getNodeById('A').isExpanded()).toBe(true);
1324 expect(store.getNodeById('I').isExpanded()).toBe(true);
1325 expect(store.getNodeById('M').isExpanded()).toBe(true);
1326 });
1327
1328 it("should expand all nodes underneath the expanded node if deep is set", function(){
1329 var store = tree.getStore();
1330 tree.expandPath('/root/A');
1331 tree.expandNode(store.getNodeById('A'), true);
1332 tree.collapseNode(store.getNodeById('A'), true);
1333 expect(store.getNodeById('B').isExpanded()).toBe(false);
1334 expect(store.getNodeById('F').isExpanded()).toBe(false);
1335 expect(store.getNodeById('G').isExpanded()).toBe(false);
1336 });
1337 });
1338 });
1339 });
1340
1341 describe("animations", function() {
1342 var enableFx = Ext.enableFx;
1343
1344 beforeEach(function() {
1345 makeTree = function(nodes, cfg) {
1346 cfg = cfg || {};
1347 Ext.applyIf(cfg, {
1348 renderTo: Ext.getBody(),
1349 store: new Ext.data.TreeStore({
1350 model: TreeItem,
1351 root: {
1352 secondaryId: 'root',
1353 id: 'root',
1354 text: 'Root',
1355 children: nodes
1356 }
1357 })
1358 });
1359 tree = new Ext.tree.Panel(cfg);
1360 };
1361 });
1362
1363 afterEach(function() {
1364 Ext.enableFx = enableFx;
1365 });
1366
1367 it("should enable animations when Ext.enableFx is true", function() {
1368 Ext.enableFx = true;
1369
1370 makeTree();
1371
1372 expect(tree.enableAnimations).toBeTruthy();
1373 });
1374
1375 it("should disable animations when Ext.enableFx is false", function() {
1376 Ext.enableFx = false;
1377
1378 makeTree();
1379
1380 expect(tree.enableAnimations).toBeFalsy();
1381 });
1382 });
1383
1384 describe('event order', function() {
1385 it("should fire 'beforeitemexpand' before 'beforeload'", function() {
1386 var order = 0,
1387 beforeitemexpandOrder,
1388 beforeloadOrder,
1389 loadOrder,
1390 layoutCounter;
1391
1392 makeTree(null, {
1393 store: new Ext.data.TreeStore({
1394 proxy: {
1395 type: 'ajax',
1396 url: 'fakeUrl'
1397 },
1398 root: {
1399 text: 'Ext JS',
1400 id: 'src'
1401 },
1402 folderSort: true,
1403 sorters: [{
1404 property: 'text',
1405 direction: 'ASC'
1406 }]
1407 }),
1408 listeners: {
1409 beforeitemexpand: function() {
1410 beforeitemexpandOrder = order;
1411 order++;
1412 },
1413 beforeload : function() {
1414 beforeloadOrder = order;
1415 order++;
1416 },
1417 load : function() {
1418 loadOrder = order;
1419 }
1420 }
1421 });
1422 layoutCounter = tree.layoutCounter;
1423 tree.getStore().getRoot().expand();
1424
1425 Ext.Ajax.mockComplete({
1426 status: 200,
1427 responseText: Ext.encode(testNodes)
1428 });
1429
1430 // The order of events expected: beforeitemexpand, beforeload, load.
1431 expect(beforeitemexpandOrder).toBe(0);
1432 expect(beforeloadOrder).toBe(1);
1433 expect(loadOrder).toBe(2);
1434
1435 // The loading plus expand of the root should only have triggered one layout
1436 expect(tree.layoutCounter).toBe(layoutCounter + 1);
1437 });
1438 });
1439
1440 describe("selected/focused/hover css classes", function() {
1441 var proto = Ext.view.Table.prototype,
1442 selectedItemCls = proto.selectedItemCls,
1443 focusedItemCls = proto.focusedItemCls,
1444 view, store, rec;
1445
1446 beforeEach(function() {
1447 makeTree(testNodes, {
1448 rowLines: true,
1449 selModel: {
1450 selType: 'rowmodel',
1451 mode: 'MULTI'
1452 }
1453 });
1454 tree.getRootNode().expand();
1455 view = tree.view;
1456 store = tree.store;
1457 });
1458
1459 function blurActiveEl() {
1460 Ext.getBody().focus();
1461 }
1462
1463 it("should preserve the selected classes when nodes are expanded", function() {
1464 tree.selModel.select([store.getNodeById('A'), store.getNodeById('M')]);
1465 store.getNodeById('A').expand();
1466 store.getNodeById('I').expand();
1467
1468 expect(view.getNodeByRecord(store.getNodeById('A'))).toHaveCls(selectedItemCls);
1469 expect(view.getNodeByRecord(store.getNodeById('M'))).toHaveCls(selectedItemCls);
1470 });
1471
1472 it("should preserve the focused classes when nodes are expanded", function() {
1473 rec = store.getNodeById('I');
1474 tree.getView().getNavigationModel().setPosition(rec);
1475 store.getNodeById('A').expand();
1476 expect(view.getCell(rec, view.getVisibleColumnManager().getColumns()[0])).toHaveCls(focusedItemCls);
1477 });
1478
1479 it("should update the selected classes when rows are collapsed", function() {
1480 store.getNodeById('A').expand();
1481 store.getNodeById('M').expand();
1482 tree.selModel.select([store.getNodeById('B'), store.getNodeById('M')]);
1483 blurActiveEl(); // EXTJSIV-11281: make sure we're not relying on dom focus for removal of focus border
1484 store.getNodeById('A').collapse();
1485 store.getNodeById('M').collapse();
1486
1487 expect(view.getNodeByRecord(store.getNodeById('M'))).toHaveCls(selectedItemCls);
1488 });
1489 });
1490
1491 describe("renderer", function() {
1492 var CustomTreeColumnNoScope = Ext.define(null, {
1493 extend: 'Ext.tree.Column',
1494
1495 renderColText: function(v) {
1496 return v + 'NoScope';
1497 },
1498 renderer: 'renderColText'
1499 }),
1500 CustomTreeColumnScopeThis = Ext.define(null, {
1501 extend: 'Ext.tree.Column',
1502
1503 renderColText: function(v) {
1504 return v + 'ScopeThis';
1505 },
1506 renderer: 'renderColText',
1507 scope: 'this'
1508 }),
1509 CustomTreeColumnScopeController = Ext.define(null, {
1510 extend: 'Ext.tree.Column',
1511 scope: 'controller'
1512 }),
1513 TreeRendererTestController = Ext.define(null, {
1514 extend: 'Ext.app.ViewController',
1515 renderColText: function(v) {
1516 return v + 'ViewController';
1517 }
1518 });
1519
1520 describe('String renderer in a column subclass', function() {
1521 it("should be able to use a named renderer in the column with no scope", function() {
1522 tree = new Ext.tree.Panel({
1523 animate: false,
1524 renderTo: Ext.getBody(),
1525 store: new Ext.data.TreeStore({
1526 model: TreeItem,
1527 root: {
1528 id: 'root',
1529 text: 'Root'
1530 }
1531 }),
1532 columns: [new CustomTreeColumnNoScope({
1533 flex: 1,
1534 dataIndex: 'text'
1535 })]
1536 });
1537 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootNoScope');
1538 });
1539 it("should be able to use a named renderer in the column with scope: 'this'", function() {
1540 tree = new Ext.tree.Panel({
1541 animate: false,
1542 renderTo: Ext.getBody(),
1543 store: new Ext.data.TreeStore({
1544 model: TreeItem,
1545 root: {
1546 id: 'root',
1547 text: 'Root'
1548 }
1549 }),
1550 columns: [new CustomTreeColumnScopeThis({
1551 flex: 1,
1552 dataIndex: 'text'
1553 })]
1554 });
1555 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootScopeThis');
1556 });
1557 // Note: xit because thrown errors inside the TableView rendering path leaves an invalid state
1558 // which breaks ALL subsequent tests.
1559 xit("should not be able to use a named renderer in the column with scope: 'controller'", function() {
1560 expect(function() {
1561 tree = new Ext.tree.Panel({
1562 animate: false,
1563 store: new Ext.data.TreeStore({
1564 model: TreeItem,
1565 root: {
1566 id: 'root',
1567 text: 'Root'
1568 }
1569 }),
1570 columns: [new CustomTreeColumnScopeController({
1571 flex: 1,
1572 dataIndex: 'text',
1573 renderer: 'renderColText',
1574 scope: 'controller'
1575 })]
1576 });
1577 tree.render(document.body);
1578 }).toThrow();
1579 });
1580 it("should be able to use a named renderer in a ViewController", function() {
1581 tree = new Ext.tree.Panel({
1582 controller: new TreeRendererTestController(),
1583 animate: false,
1584 renderTo: Ext.getBody(),
1585 store: new Ext.data.TreeStore({
1586 model: TreeItem,
1587 root: {
1588 id: 'root',
1589 text: 'Root'
1590 }
1591 }),
1592 columns: [new CustomTreeColumnNoScope({
1593 flex: 1,
1594 dataIndex: 'text',
1595 renderer: 'renderColText'
1596 })]
1597 });
1598 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1599 tree.destroy();
1600
1601 tree = new Ext.tree.Panel({
1602 controller: new TreeRendererTestController(),
1603 animate: false,
1604 renderTo: Ext.getBody(),
1605 store: new Ext.data.TreeStore({
1606 model: TreeItem,
1607 root: {
1608 id: 'root',
1609 text: 'Root'
1610 }
1611 }),
1612 columns: [new CustomTreeColumnScopeController({
1613 flex: 1,
1614 dataIndex: 'text',
1615 renderer: 'renderColText'
1616 })]
1617 });
1618 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1619 tree.destroy();
1620
1621 tree = new Ext.tree.Panel({
1622 animate: false,
1623 renderTo: Ext.getBody(),
1624 store: new Ext.data.TreeStore({
1625 model: TreeItem,
1626 root: {
1627 id: 'root',
1628 text: 'Root'
1629 }
1630 }),
1631 columns: [new CustomTreeColumnNoScope({
1632 controller: new TreeRendererTestController(),
1633 flex: 1,
1634 dataIndex: 'text',
1635 renderer: 'renderColText',
1636 scope: 'self.controller'
1637 })]
1638 });
1639 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1640 });
1641 it("should be able to use a named renderer in the Column with no scope when Column uses defaultListenerScope: true", function() {
1642 tree = new Ext.tree.Panel({
1643 animate: false,
1644 renderTo: Ext.getBody(),
1645 store: new Ext.data.TreeStore({
1646 model: TreeItem,
1647 root: {
1648 id: 'root',
1649 text: 'Root'
1650 }
1651 }),
1652 columns: [new CustomTreeColumnNoScope({
1653 defaultListenerScope: true,
1654 flex: 1,
1655 dataIndex: 'text',
1656 renderColText: function(v) {
1657 return v + 'ColDefaultScope';
1658 },
1659 renderer: 'renderColText'
1660 })]
1661 });
1662 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootColDefaultScope');
1663 });
1664 it("should be able to use a named renderer in the Panel with no scope when Panel uses defaultListenerScope: true", function() {
1665 tree = new Ext.tree.Panel({
1666 animate: false,
1667 renderTo: Ext.getBody(),
1668 store: new Ext.data.TreeStore({
1669 model: TreeItem,
1670 root: {
1671 id: 'root',
1672 text: 'Root'
1673 }
1674 }),
1675 defaultListenerScope: true,
1676 panelRenderColText: function(v) {
1677 return v + 'PanelDefaultScope';
1678 },
1679 columns: [new CustomTreeColumnNoScope({
1680 flex: 1,
1681 dataIndex: 'text',
1682 renderer: 'panelRenderColText'
1683 })]
1684 });
1685 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootPanelDefaultScope');
1686 });
1687 });
1688
1689 describe('String renderer in a column definition', function() {
1690 it("should be able to use a named renderer in the column with no scope", function() {
1691 tree = new Ext.tree.Panel({
1692 animate: false,
1693 renderTo: Ext.getBody(),
1694 store: new Ext.data.TreeStore({
1695 model: TreeItem,
1696 root: {
1697 id: 'root',
1698 text: 'Root'
1699 }
1700 }),
1701 columns: [{
1702 xtype: 'treecolumn',
1703 flex: 1,
1704 dataIndex: 'text',
1705 renderColText: function(v) {
1706 return v + 'NoScope';
1707 },
1708 renderer: 'renderColText'
1709 }]
1710 });
1711 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootNoScope');
1712 });
1713 it("should be able to use a named renderer in the column with scope: 'this'", function() {
1714 tree = new Ext.tree.Panel({
1715 animate: false,
1716 renderTo: Ext.getBody(),
1717 store: new Ext.data.TreeStore({
1718 model: TreeItem,
1719 root: {
1720 id: 'root',
1721 text: 'Root'
1722 }
1723 }),
1724 columns: [{
1725 xtype: 'treecolumn',
1726 flex: 1,
1727 dataIndex: 'text',
1728 renderColText: function(v) {
1729 return v + 'ScopeThis';
1730 },
1731 renderer: 'renderColText',
1732 scope: 'this'
1733 }]
1734 });
1735 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootScopeThis');
1736 });
1737 // Note: xit because thrown errors inside the TableView rendering path leaves an invalid state
1738 // which breaks ALL subsequent tests.
1739 xit("should not be able to use a named renderer in the column with scope: 'controller'", function() {
1740 expect(function() {
1741 tree = new Ext.tree.Panel({
1742 animate: false,
1743 store: new Ext.data.TreeStore({
1744 model: TreeItem,
1745 root: {
1746 id: 'root',
1747 text: 'Root'
1748 }
1749 }),
1750 columns: [{
1751 xtype: 'treecolumn',
1752 flex: 1,
1753 dataIndex: 'text',
1754 renderColText: function(v) {
1755 return v + 'Foo';
1756 },
1757 renderer: 'renderColText',
1758 scope: 'controller'
1759 }]
1760 });
1761 tree.render(document.body);
1762 }).toThrow();
1763 });
1764 it("should be able to use a named renderer in a ViewController", function() {
1765 tree = new Ext.tree.Panel({
1766 controller: new TreeRendererTestController(),
1767 animate: false,
1768 renderTo: Ext.getBody(),
1769 store: new Ext.data.TreeStore({
1770 model: TreeItem,
1771 root: {
1772 id: 'root',
1773 text: 'Root'
1774 }
1775 }),
1776 columns: [{
1777 xtype: 'treecolumn',
1778 flex: 1,
1779 dataIndex: 'text',
1780 renderer: 'renderColText'
1781 }]
1782 });
1783 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1784 tree.destroy();
1785
1786 tree = new Ext.tree.Panel({
1787 controller: new TreeRendererTestController(),
1788 animate: false,
1789 renderTo: Ext.getBody(),
1790 store: new Ext.data.TreeStore({
1791 model: TreeItem,
1792 root: {
1793 id: 'root',
1794 text: 'Root'
1795 }
1796 }),
1797 columns: [{
1798 xtype: 'treecolumn',
1799 flex: 1,
1800 dataIndex: 'text',
1801 renderer: 'renderColText',
1802 scope: 'controller'
1803 }]
1804 });
1805 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1806 tree.destroy();
1807
1808 tree = new Ext.tree.Panel({
1809 animate: false,
1810 renderTo: Ext.getBody(),
1811 store: new Ext.data.TreeStore({
1812 model: TreeItem,
1813 root: {
1814 id: 'root',
1815 text: 'Root'
1816 }
1817 }),
1818 columns: [{
1819 controller: new TreeRendererTestController(),
1820 xtype: 'treecolumn',
1821 flex: 1,
1822 dataIndex: 'text',
1823 renderer: 'renderColText',
1824 scope: 'self.controller'
1825 }]
1826 });
1827 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootViewController');
1828 });
1829 it("should be able to use a named renderer in the Column with no scope when Column uses defaultListenerScope: true", function() {
1830 tree = new Ext.tree.Panel({
1831 animate: false,
1832 renderTo: Ext.getBody(),
1833 store: new Ext.data.TreeStore({
1834 model: TreeItem,
1835 root: {
1836 id: 'root',
1837 text: 'Root'
1838 }
1839 }),
1840 columns: [{
1841 xtype: 'treecolumn',
1842 defaultListenerScope: true,
1843 flex: 1,
1844 dataIndex: 'text',
1845 renderColText: function(v) {
1846 return v + 'ColDefaultScope';
1847 },
1848 renderer: 'renderColText'
1849 }]
1850 });
1851 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootColDefaultScope');
1852 });
1853 it("should be able to use a named renderer in the Panel with no scope when Panel uses defaultListenerScope: true", function() {
1854 tree = new Ext.tree.Panel({
1855 animate: false,
1856 renderTo: Ext.getBody(),
1857 store: new Ext.data.TreeStore({
1858 model: TreeItem,
1859 root: {
1860 id: 'root',
1861 text: 'Root'
1862 }
1863 }),
1864 defaultListenerScope: true,
1865 panelRenderColText: function(v) {
1866 return v + 'PanelDefaultScope';
1867 },
1868 columns: [{
1869 xtype: 'treecolumn',
1870 flex: 1,
1871 dataIndex: 'text',
1872 renderer: 'panelRenderColText'
1873 }]
1874 });
1875 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootPanelDefaultScope');
1876 });
1877 });
1878
1879 it("should be able to use a renderer to render the value", function() {
1880 tree = new Ext.tree.Panel({
1881 animate: false,
1882 renderTo: Ext.getBody(),
1883 store: new Ext.data.TreeStore({
1884 model: TreeItem,
1885 root: {
1886 id: 'root',
1887 text: 'Root'
1888 }
1889 }),
1890 columns: [{
1891 xtype: 'treecolumn',
1892 flex: 1,
1893 dataIndex: 'text',
1894 renderer: function(v) {
1895 return v + 'Foo';
1896 }
1897 }]
1898 });
1899 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('RootFoo');
1900 });
1901
1902 it("should be able to use a string renderer that maps to Ext.util.Format", function () {
1903 tree = new Ext.tree.Panel({
1904 animate: false,
1905 renderTo: Ext.getBody(),
1906 store: new Ext.data.TreeStore({
1907 model: TreeItem,
1908 root: {
1909 id: 'root',
1910 text: 'Root'
1911 }
1912 }),
1913 columns: [{
1914 xtype: 'treecolumn',
1915 flex: 1,
1916 formatter: 'uppercase',
1917 dataIndex: 'text'
1918 }]
1919 });
1920 expect(tree.el.down('.x-tree-node-text').dom.innerHTML).toEqual('ROOT');
1921 });
1922 });
1923
1924
1925 // https://sencha.jira.com/browse/EXTJSIV-9533
1926 describe('programmatic load', function() {
1927 beforeEach(function() {
1928 Ext.define('spec.Foo', {
1929 extend : 'Ext.data.Model',
1930 fields : ['Name', 'Id'],
1931 idProperty : 'Id'
1932 });
1933 });
1934
1935 afterEach(function () {
1936 Ext.undefine('spec.Foo');
1937 Ext.data.Model.schema.clear(true);
1938 });
1939
1940 function getData() {
1941 return [{
1942 "BaselineEndDate" : "2010-02-01",
1943 "Id" : 1,
1944 "Name" : "Planning",
1945 "PercentDone" : 50,
1946 "StartDate" : "2010-01-18",
1947 "BaselineStartDate" : "2010-01-13",
1948 "Duration" : 11,
1949 "expanded" : true,
1950 "TaskType" : "Important",
1951 "children" : [{
1952 "BaselineEndDate" : "2010-01-28",
1953 "Id" : 11,
1954 "leaf" : true,
1955 "Name" : "Investigate",
1956 "PercentDone" : 50,
1957 "TaskType" : "LowPrio",
1958 "StartDate" : "2010-01-18",
1959 "BaselineStartDate" : "2010-01-20",
1960 "Duration" : 10
1961 }, {
1962 "BaselineEndDate" : "2010-02-01",
1963 "Id" : 12,
1964 "leaf" : true,
1965 "Name" : "Assign resources",
1966 "PercentDone" : 50,
1967 "StartDate" : "2010-01-18",
1968 "BaselineStartDate" : "2010-01-25",
1969 "Duration" : 10
1970 }, {
1971 "BaselineEndDate" : "2010-02-01",
1972 "Id" : 13,
1973 "leaf" : true,
1974 "Name" : "Gather documents (not resizable)",
1975 "Resizable" : false,
1976 "PercentDone" : 50,
1977 "StartDate" : "2010-01-18",
1978 "BaselineStartDate" : "2010-01-25",
1979 "Duration" : 10
1980 }, {
1981 "BaselineEndDate" : "2010-02-04",
1982 "Id" : 17,
1983 "leaf" : true,
1984 "Name" : "Report to management",
1985 "TaskType" : "Important",
1986 "PercentDone" : 0,
1987 "StartDate" : "2010-02-02",
1988 "BaselineStartDate" : "2010-02-04",
1989 "Duration" : 0
1990 }]
1991 }];
1992 }
1993
1994 it('should reload the root node', function() {
1995 var store = new Ext.data.TreeStore({
1996 model : 'spec.Foo',
1997 proxy : {
1998 type : 'ajax',
1999 url : '/data/AjaxProxy/treeLoadData'
2000 },
2001 root : {
2002 Name : 'ROOOOOOOOT',
2003 expanded : true
2004 }
2005 }), refreshSpy;
2006
2007 tree = new Ext.tree.Panel({
2008 renderTo : Ext.getBody(),
2009 width : 600,
2010 height : 400,
2011 store : store,
2012 viewConfig: {
2013 loadMask: false
2014 },
2015 columns : [{
2016 xtype : 'treecolumn',
2017 header : 'Tasks',
2018 dataIndex : 'Name',
2019 locked : true,
2020 width : 200
2021 }, {
2022 width : 200,
2023 dataIndex : 'Id'
2024 }]
2025 });
2026
2027 Ext.Ajax.mockComplete({
2028 status: 200,
2029 responseText: Ext.encode(getData())
2030 });
2031
2032 var lockedView = tree.lockedGrid.view,
2033 normalView = tree.normalGrid.view;
2034
2035 refreshSpy = spyOnEvent(store, 'refresh');
2036 store.load();
2037
2038 Ext.Ajax.mockComplete({
2039 status: 200,
2040 responseText: Ext.encode(getData())
2041 });
2042
2043 expect(refreshSpy.callCount).toBe(1);
2044 expect(lockedView.getNodes().length).toBe(6);
2045 expect(normalView.getNodes().length).toBe(6);
2046 });
2047 });
2048
2049 describe('filtering', function() {
2050 var treeData = [{
2051 text: 'Top 1',
2052 children: [{
2053 text: 'foo',
2054 leaf: true
2055 }, {
2056 text: 'bar',
2057 leaf: true
2058 }, {
2059 text: 'Second level 1',
2060 children: [{
2061 text: 'foo',
2062 leaf: true
2063 }, {
2064 text: 'bar',
2065 leaf: true
2066 }]
2067 }]
2068 }, {
2069 text: 'Top 2',
2070 children: [{
2071 text: 'foo',
2072 leaf: true
2073 }, {
2074 text: 'wonk',
2075 leaf: true
2076 }, {
2077 text: 'Second level 2',
2078 children: [{
2079 text: 'foo',
2080 leaf: true
2081 }, {
2082 text: 'wonk',
2083 leaf: true
2084 }]
2085 }]
2086 }, {
2087 text: 'Top 3',
2088 children: [{
2089 text: 'zarg',
2090 leaf: true
2091 }, {
2092 text: 'bar',
2093 leaf: true
2094 }, {
2095 text: 'Second level 3',
2096 children: [{
2097 text: 'zarg',
2098 leaf: true
2099 }, {
2100 text: 'bar',
2101 leaf: true
2102 }]
2103 }]
2104 }];
2105
2106 beforeEach(function() {
2107 makeTree(treeData, {
2108 rootVisible: false
2109 });
2110 });
2111
2112 function testRowText(rowIdx, value) {
2113 return view.store.getAt(rowIdx).get('text') === value;
2114 }
2115
2116 it('should only show nodes which pass a filter', function() {
2117 // When filtering the updating of the 'visible' field must not percolate a store update event out to views.
2118 var handleUpdateCallCount,
2119 handleUpdateSpy = spyOn(view, 'handleUpdate').andCallThrough();
2120
2121 // Check correct initial state
2122 expect(view.all.getCount()).toBe(3);
2123 expect(view.store.getCount()).toBe(3);
2124 expect(testRowText(0, 'Top 1')).toBe(true);
2125 expect(testRowText(1, 'Top 2')).toBe(true);
2126 expect(testRowText(2, 'Top 3')).toBe(true);
2127
2128 // Filter so that only "foo" nodes and their ancestors are visible
2129 store.filter({
2130 filterFn: function(node) {
2131 var children = node.childNodes,
2132 len = children && children.length,
2133
2134 // Visibility of leaf nodes is whether they pass the test.
2135 // Visibility of branch nodes depends on them having visible children.
2136 visible = node.isLeaf() ? node.get('text') === 'foo' : false,
2137 i;
2138
2139 // We're visible if one of our child nodes is visible.
2140 // No loop body here. We are looping only while the visible flag remains false.
2141 // Child nodes are filtered before parents, so we can check them here.
2142 // As soon as we find a visible child, this branch node must be visible.
2143 for (i = 0; i < len && !(visible = children[i].get('visible')); i++);
2144
2145 return visible;
2146 },
2147 id: 'testFilter'
2148 });
2149
2150 // The setting of the visible field in the filtered out record should NOT have resulted
2151 // in any update events firing to the view.
2152 expect(handleUpdateSpy.callCount).toBe(0);
2153
2154 rootNode.childNodes[0].expand();
2155
2156 // The "Second level 1" branch node is visible because it has a child with text "foo"
2157 expect(view.all.getCount()).toBe(4);
2158 expect(view.store.getCount()).toBe(4);
2159 expect(testRowText(0, 'Top 1')).toBe(true);
2160 expect(testRowText(1, 'foo')).toBe(true);
2161 expect(testRowText(2, 'Second level 1')).toBe(true);
2162 expect(testRowText(3, 'Top 2')).toBe(true);
2163
2164 // Expand "Second level 1". It contains 1 "foo" child.
2165 rootNode.childNodes[0].childNodes[2].expand();
2166
2167 expect(view.all.getCount()).toBe(5);
2168 expect(view.store.getCount()).toBe(5);
2169 expect(testRowText(0, 'Top 1')).toBe(true);
2170 expect(testRowText(1, 'foo')).toBe(true);
2171 expect(testRowText(2, 'Second level 1')).toBe(true);
2172 expect(testRowText(3, 'foo')).toBe(true);
2173 expect(testRowText(4, 'Top 2')).toBe(true);
2174
2175 // The spy will have been called now because of node expansion setting the expanded field,
2176 // resulting in the updating of the folder icon in the view.
2177 // We are going to check that the filter operation below does NOT increment it.
2178 handleUpdateCallCount = handleUpdateSpy.callCount;
2179
2180 // Now, with "Top 1" amd "Second level 1" already expanded, let's see only "bar" nodes and their ancestors.
2181 // View should refresh.
2182 store.filter({
2183 filterFn: function(node) {
2184 var children = node.childNodes,
2185 len = children && children.length,
2186
2187 // Visibility of leaf nodes is whether they pass the test.
2188 // Visibility of branch nodes depends on them having visible children.
2189 visible = node.isLeaf() ? node.get('text') === 'bar' : false,
2190 i;
2191
2192 // We're visible if one of our child nodes is visible.
2193 // No loop body here. We are looping only while the visible flag remains false.
2194 // Child nodes are filtered before parents, so we can check them here.
2195 // As soon as we find a visible child, this branch node must be visible.
2196 for (i = 0; i < len && !(visible = children[i].get('visible')); i++);
2197
2198 return visible;
2199 },
2200 id: 'testFilter'
2201 });
2202
2203 // The setting of the visible field in the filtered out record should NOT have resulted
2204 // in any update events firing to the view.
2205 expect(handleUpdateSpy.callCount).toBe(handleUpdateCallCount);
2206
2207 expect(view.all.getCount()).toBe(5);
2208 expect(view.store.getCount()).toBe(5);
2209 expect(testRowText(0, 'Top 1')).toBe(true);
2210 expect(testRowText(1, 'bar')).toBe(true);
2211 expect(testRowText(2, 'Second level 1')).toBe(true);
2212 expect(testRowText(3, 'bar')).toBe(true);
2213 expect(testRowText(4, 'Top 3')).toBe(true);
2214
2215 // Expand "Top 3". It contains a "bar" and "Second level3", which should be visible because it contains a "bar"
2216 rootNode.childNodes[2].expand();
2217
2218 expect(view.all.getCount()).toBe(7);
2219 expect(view.store.getCount()).toBe(7);
2220 expect(testRowText(0, 'Top 1')).toBe(true);
2221 expect(testRowText(1, 'bar')).toBe(true);
2222 expect(testRowText(2, 'Second level 1')).toBe(true);
2223 expect(testRowText(3, 'bar')).toBe(true);
2224 expect(testRowText(4, 'Top 3')).toBe(true);
2225 expect(testRowText(5, 'bar')).toBe(true);
2226 expect(testRowText(6, 'Second level 3')).toBe(true);
2227
2228 // Collapse "Top 3". The "bar" and "Second level3" which contains a "bar" should disappear
2229 rootNode.childNodes[2].collapse();
2230
2231 expect(view.all.getCount()).toBe(5);
2232 expect(view.store.getCount()).toBe(5);
2233 expect(testRowText(0, 'Top 1')).toBe(true);
2234 expect(testRowText(1, 'bar')).toBe(true);
2235 expect(testRowText(2, 'Second level 1')).toBe(true);
2236 expect(testRowText(3, 'bar')).toBe(true);
2237 expect(testRowText(4, 'Top 3')).toBe(true);
2238
2239 // Collapse the top level nodes
2240 // So now only top levels which contain a "bar" somewhere in their hierarchy should be visible.
2241 rootNode.collapseChildren();
2242 expect(view.all.getCount()).toBe(2);
2243 expect(view.store.getCount()).toBe(2);
2244 expect(testRowText(0, 'Top 1')).toBe(true);
2245 expect(testRowText(1, 'Top 3')).toBe(true);
2246 });
2247 });
2248
2249 describe('sorting', function() {
2250 it('should sort nodes', function() {
2251 var bNode;
2252
2253 makeTree(testNodes, null, {
2254 folderSort: true,
2255 sorters: [{
2256 property: 'text',
2257 direction: 'ASC'
2258 }]
2259 });
2260 tree.expandAll();
2261 bNode = tree.store.getNodeById('B');
2262
2263 // Insert an out of order node.
2264 // MUST be leaf: true so that the automatically prepended sort by leaf status has no effect.
2265 bNode.insertChild(0, {
2266 text:'Z',
2267 leaf: true
2268 });
2269
2270 // Check that we have disrupted the sorted state.
2271 expect(bNode.childNodes[0].get('text')).toBe('Z');
2272 expect(bNode.childNodes[1].get('text')).toBe('C');
2273 expect(bNode.childNodes[2].get('text')).toBe('D');
2274
2275 // Sort using the owning TreeStore's sorter set.
2276 // It is by leaf status, then text, ASC.
2277 // These are all leaf nodes.
2278 bNode.sort();
2279 expect(bNode.childNodes[0].get('text')).toBe('C');
2280 expect(bNode.childNodes[1].get('text')).toBe('D');
2281 expect(bNode.childNodes[2].get('text')).toBe('Z');
2282
2283 // Sort passing a comparator which does a descending sort on text
2284 bNode.sort(function(node1, node2) {
2285 return node1.get('text') > node2.get('text') ? -1 : 1;
2286 });
2287 expect(bNode.childNodes[0].get('text')).toBe('Z');
2288 expect(bNode.childNodes[1].get('text')).toBe('D');
2289 expect(bNode.childNodes[2].get('text')).toBe('C');
2290 });
2291 });
2292
2293 describe('Buffered rendering large, expanded root node', function() {
2294 function makeNodes() {
2295 var nodes = [],
2296 i, j,
2297 ip1, jp1,
2298 node;
2299
2300 for (i = 0; i < 50; i++) {
2301 ip1 = i + 1;
2302 node = {
2303 id: 'n' + ip1,
2304 text: 'Node' + ip1,
2305 children: [
2306
2307 ]
2308 };
2309 for (j = 0; j < 50; j++) {
2310 jp1 = j + 1;
2311 node.children.push({
2312 id: 'n' + ip1 + '.' + jp1,
2313 text: 'Node' + ip1 + '/' + jp1,
2314 leaf: true
2315 });
2316 }
2317 nodes.push(node);
2318 }
2319 return nodes;
2320 }
2321
2322 function completeWithNodes() {
2323 Ext.Ajax.mockComplete({
2324 status: 200,
2325 responseText: Ext.encode(makeNodes())
2326 });
2327 }
2328
2329 it('should maintain scroll position on reload', function() {
2330 makeTree(null, {
2331 height: 400,
2332 width: 350
2333 }, {
2334 proxy: {
2335 type: 'ajax',
2336 url: '/tree/Panel/load'
2337 },
2338 root: {
2339 id: 'root',
2340 text: 'Root',
2341 expanded: true
2342 }
2343 });
2344
2345 completeWithNodes();
2346
2347 view.setScrollY(500);
2348 store.reload();
2349
2350 completeWithNodes();
2351
2352 expect(view.getScrollY()).toBe(500);
2353 });
2354
2355 it('should negate the animate flag and not throw an error', function() {
2356 makeTree(null, {
2357 height: 400,
2358 width: 350,
2359 animate: true
2360 }, {
2361 proxy: {
2362 type: 'ajax',
2363 url: '/tree/Panel/load'
2364 },
2365 root: {
2366 id: 'root',
2367 text: 'Root',
2368 expanded: true
2369 }
2370 });
2371 completeWithNodes();
2372
2373 // EXTJS-13673 buffered rendering should be turned on by default
2374 expect(tree.view.bufferedRenderer instanceof Ext.grid.plugin.BufferedRenderer).toBe(true);
2375 });
2376
2377 it('should scroll to unloaded nodes by absolute path', function() {
2378 makeTree(null, {
2379 height: 400,
2380 width: 350
2381 }, {// lazyFill means childNodes do not load locally available children arrays until expanded.
2382 lazyFill: true,
2383 proxy: {
2384 type: 'ajax',
2385 url: '/tree/Panel/load'
2386 },
2387 root: {
2388 id: 'root',
2389 text: 'Root',
2390 expanded: false
2391 }
2392 });
2393
2394 // forces the root to load even though we configure it expanded: false.
2395 // We want to exercise the ability of pathing to expand all the way from the root.
2396 store.load();
2397
2398 completeWithNodes();
2399
2400 tree.ensureVisible('/root/n50/n50.50');
2401 expect(Ext.fly(view.getNode(store.getById('n50.50'))).getBox().bottom).toBeLessThanOrEqual(view.getBox().bottom);
2402 });
2403
2404 it('should throw an error when being asked to scroll to an invisible root node', function() {
2405 makeTree(null, {
2406 height: 400,
2407 width: 350,
2408 rootVisible: false
2409 }, {
2410 // lazyFill means childNodes do not load locally available children arrays until expanded.
2411 lazyFill: true,
2412 proxy: {
2413 type: 'ajax',
2414 url: '/tree/Panel/load'
2415 },
2416 root: {
2417 id: 'root',
2418 text: 'Root',
2419 expanded: true
2420 }
2421 });
2422
2423 // forces the root to load even though we configure it expanded: false.
2424 // We want to exercise the ability of pathing to expand all the way from the root.
2425 store.load();
2426
2427 completeWithNodes();
2428
2429 runs(function() {
2430 expect(function() {
2431 tree.ensureVisible(rootNode);
2432 }).toThrow('Unknown record passed to BufferedRenderer#scrollTo');
2433 });
2434 });
2435
2436 it('should scroll to loaded nodes by relative path', function() {
2437 makeTree(null, {
2438 height: 400,
2439 width: 350
2440 }, {
2441 proxy: {
2442 type: 'ajax',
2443 url: '/tree/Panel/load'
2444 },
2445 root: {
2446 id: 'root',
2447 text: 'Root',
2448 expanded: false
2449 }
2450 });
2451
2452 // forces the root to load even though we configure it expanded: false.
2453 // We want to exercise the ability of pathing to expand all the way from the root.
2454 store.load();
2455
2456 completeWithNodes();
2457
2458 runs(function() {
2459 tree.ensureVisible('n50.50');
2460 expect(Ext.fly(view.getNode(store.getById('n50.50'))).getBox().bottom).toBeLessThanOrEqual(view.getBox().bottom);
2461 });
2462 });
2463 });
2464
2465 describe('multi append node', function() {
2466 var layoutCounter,
2467 height;
2468
2469 beforeEach(function() {
2470 makeTree(testNodes, null, null, {
2471 expanded: true
2472 });
2473 layoutCounter = view.componentLayoutCounter;
2474 });
2475
2476 it('should only update the view once when an array of nodes is passed', function() {
2477 height = tree.getHeight();
2478 expect(view.all.getCount()).toEqual(4);
2479 tree.getRootNode().appendChild([{
2480 id: 'append-1',
2481 text: 'append-1',
2482 secondaryId: 'append-1'
2483 }, {
2484 id: 'append-2',
2485 text: 'append-2',
2486 secondaryId: 'append-2'
2487 }, {
2488 id: 'append-3',
2489 text: 'append-3',
2490 secondaryId: 'append-3'
2491 }, {
2492 id: 'append-4',
2493 text: 'append-4',
2494 secondaryId: 'append-4'
2495 }, {
2496 id: 'append-5',
2497 text: 'append-5',
2498 secondaryId: 'append-5'
2499 }]);
2500
2501 // We added 5 nodes
2502 expect(view.all.getCount()).toEqual(9);
2503
2504 // We are shrinkwrap height, so it shuold have grown
2505 expect(tree.getHeight()).toBeGreaterThan(height);
2506
2507 // All should have been done in one, rather than one update per node
2508 expect(view.componentLayoutCounter).toEqual(layoutCounter + 1);
2509 });
2510 });
2511
2512 describe('tracking removed nodes', function() {
2513 it('should not add nodes removed by virtue of their parent collapsing to the removed list', function() {
2514 var done = false;
2515 makeTree(testNodes, null, {
2516 trackRemoved: true
2517 });
2518 tree.expandAll(function() {
2519 tree.collapseAll(function() {
2520 done = true;
2521 });
2522 });
2523 waitsFor(function() {
2524 return done;
2525 });
2526 runs(function() {
2527 expect(tree.store.getRemovedRecords().length).toBe(0);
2528 });
2529 });
2530
2531 it('should add descendants of collapsed nodes to the removed list', function() {
2532 // Create tree with collapsed root node;
2533 makeTree(testNodes, null, {
2534 trackRemoved: true
2535 });
2536 runs(function() {
2537 tree.store.getRootNode().drop();
2538
2539 // All nodes, even though they are not present in the store's Collection should have been added to the tracked list
2540 expect(tree.store.getRemovedRecords().length).toBe(14);
2541 });
2542 });
2543
2544 it('should add descendants of filtered out nodes to the removed list', function() {
2545 var done = false;
2546
2547 // Create tree with collapsed root node;
2548 makeTree(testNodes, null, {
2549 trackRemoved: true
2550 });
2551 tree.expandAll(function() {
2552 done = true;
2553 });
2554 waitsFor(function() {
2555 return done;
2556 });
2557
2558 // When all are expanded, filter them all out.
2559 // Dropping the root node should still remove all descendants
2560 runs(function() {
2561 tree.store.filter('id', 'all_nodes_filtered_out');
2562
2563 // Filtering should not add to remove list
2564 expect(tree.store.getRemovedRecords().length).toBe(0);
2565
2566 tree.store.getRootNode().drop();
2567
2568 // All nodes, even though they are not present in the store's Collection should have been added to the tracked list
2569 expect(tree.store.getRemovedRecords().length).toBe(14);
2570 });
2571 });
2572 });
2573
2574 describe('Changing root node', function() {
2575 it('should remove all listeners from old root node', function() {
2576 tree = new Ext.tree.Panel({
2577 title: 'Test',
2578 height: 200,
2579 width: 400,
2580 root: {
2581 text: 'Root',
2582 expanded: true,
2583 children: [{
2584 text: 'A',
2585 leaf: true
2586 }, {
2587 text: 'B',
2588 leaf: true
2589 }]
2590 }
2591 });
2592
2593 var oldRoot = tree.getRootNode();
2594
2595 // The old root should have some listeners
2596 expect(Ext.Object.getKeys(oldRoot.hasListeners).length).toBeGreaterThan(0);
2597
2598 tree.store.setRoot({
2599 text: 'NewRoot',
2600 expanded: true,
2601 children: [{
2602 text: 'New A',
2603 leaf: true
2604 }, {
2605 text: 'New B',
2606 leaf: true
2607 }]
2608 });
2609
2610 // The old root should have no listeners
2611 expect(Ext.Object.getKeys(oldRoot.hasListeners).length).toBe(0);
2612
2613 });
2614 });
2615
2616 describe('sorting a collapsed node', function() {
2617 it('should not expand a collapsed node upon sort', function() {
2618 makeTree(testNodes, null, {
2619 folderSort: true,
2620 sorters: [{
2621 property: 'text',
2622 direction: 'ASC'
2623 }]
2624 });
2625 rootNode.expand();
2626 var aNode = tree.store.getNodeById('A');
2627
2628 // Sort the "A" node
2629 aNode.sort(function(a, b) {
2630 return a.get('text').localeCompare(b.get('text'));
2631 });
2632
2633 // Should NOT have resulted in expansion
2634 expect(tree.store.indexOf(aNode.childNodes[0])).toBe(-1);
2635 expect(tree.store.indexOf(aNode.childNodes[1])).toBe(-1);
2636 expect(tree.store.indexOf(aNode.childNodes[2])).toBe(-1);
2637 });
2638 });
2639 });