1 describe("Ext.tree.Panel", function(){
3 var TreeItem
= Ext
.define(null, {
4 extend
: 'Ext.data.TreeModel',
5 fields
: ['id', 'text', 'secondaryId'],
10 tree
, view
, makeTree
, testNodes
, store
, rootNode
,
11 synchronousLoad
= true,
12 treeStoreLoad
= Ext
.data
.TreeStore
.prototype.load
,
15 function spyOnEvent(object
, eventName
, fn
) {
19 spy
= spyOn(obj
, "fn");
20 object
.addListener(eventName
, obj
.fn
);
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
);
34 MockAjaxManager
.addMethods();
107 makeTree = function(nodes
, cfg
, storeCfg
, rootCfg
) {
111 renderTo
: Ext
.getBody(),
115 store
: store
= new Ext
.data
.TreeStore(Ext
.apply({
125 tree
= new Ext
.tree
.Panel(cfg
);
127 rootNode
= tree
.getRootNode();
131 afterEach(function(){
132 // Undo the overrides.
133 Ext
.data
.TreeStore
.prototype.load
= treeStoreLoad
;
136 tree
= makeTree
= null;
137 MockAjaxManager
.removeMethods();
140 describe('Checkbox tree nodes', function() {
146 beforeEach(function() {
148 makeTree(testNodes
, {
150 checkchange: function(rec
) {
155 store
.getRoot().cascadeBy(function(r
) {
156 r
.set('checked', false);
159 record
= store
.getAt(1);
160 row
= Ext
.get(view
.getRow(record
));
161 checkbox
= row
.down(view
.checkboxSelector
, true);
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);
169 it('should veto checkchange if false is returned from a beforecheckchange handler', function() {
171 beforecheckchange: function(rec
) {
176 jasmine
.fireMouseEvent(checkbox
, 'click');
177 expect(eventRec
).toBe(record
);
178 expect(record
.get('checked')).toBe(false);
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() {
186 var createRowSpy
= spyOn(view
, 'createRowElement').andCallThrough();
188 rootNode
.childNodes
[0].set('cls', 'foobar');
190 expect(view
.all
.item(1).down('td').hasCls('foobar')).toBe(true);
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);
200 describe("construction", function() {
201 it("should render while the root node is loading", function() {
203 makeTree(null, null, {
215 describe("setting the root node", function() {
216 it("should set the nodes correctly when setting root on the store", function() {
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');
229 it("should set the nodes correctly when setting root on the tree", function() {
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');
242 it("should preserve events", function() {
243 var spy
= jasmine
.createSpy();
250 beforeitemcollapse
: spy
,
251 beforeitemexpand
: spy
,
255 tree
.setRootNode(root2
);
257 rootNode
= tree
.getRootNode();
258 rootNode
.childNodes
[0].expand();
259 rootNode
.childNodes
[0].collapse();
261 expect(spy
.callCount
).toBe(4);
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
,
294 var treepanel
= tree
.down('treepanel');
296 // The provided default root node has no children
297 expect(treepanel
.getRootNode().childNodes
.length
).toBe(0);
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');
306 describe("mouse click to expand/collapse", function() {
307 function makeAutoTree(animate
, data
) {
315 describe("Clicking on expander", function() {
316 it("should not fire a click event on click of expnder", function() {
317 makeAutoTree(true, [{
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);
331 // Focus must be on the tree cell upon expand
332 tree
.on('expand', function() {
333 expect(Ext
.Element
.getActiveElement
).toBe(cell10
.getCell(true));
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;
343 expect(tree
.getHeight()).toBeGreaterThan(height
);
345 // Clicking on an expander should not trigger a cell click
346 expect(cellClickSpy
).not
.toHaveBeenCalled();
348 // Clicking on an expander should not trigger an item click
349 expect(itemClickSpy
).not
.toHaveBeenCalled();
356 describe("auto height with expand/collapse", function() {
357 function makeAutoTree(animate
, data
) {
365 describe("with animate: true", function() {
366 it("should update the height after an expand animation", function() {
367 makeAutoTree(true, [{
374 var spy
= jasmine
.createSpy(),
375 height
= tree
.getHeight();
377 tree
.on('afteritemexpand', spy
);
378 tree
.getRootNode().firstChild
.expand();
379 waitsFor(function() {
380 return spy
.callCount
> 0;
383 expect(tree
.getHeight()).toBeGreaterThan(height
);
387 it("should update the height after a collapse animation", function() {
388 makeAutoTree(true, [{
395 var spy
= jasmine
.createSpy(),
396 height
= tree
.getHeight();
398 tree
.on('afteritemcollapse', spy
);
399 tree
.getRootNode().firstChild
.collapse();
400 waitsFor(function() {
401 return spy
.callCount
> 0;
404 expect(tree
.getHeight()).toBeLessThan(height
);
409 describe("with animate: false", function() {
410 it("should update the height after an expand animation", function() {
411 makeAutoTree(false, [{
419 var height
= tree
.getHeight();
420 tree
.getRootNode().firstChild
.expand();
421 expect(tree
.getHeight()).toBeGreaterThan(height
);
424 it("should update the height after a collapse animation", function() {
425 makeAutoTree(false, [{
433 var height
= tree
.getHeight();
434 tree
.getRootNode().firstChild
.collapse();
435 expect(tree
.getHeight()).toBeLessThan(height
);
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
448 testNodes
[0].expanded
= true;
450 makeTree(testNodes
, {
451 renderTo
: document
.body
,
459 it("should collapse correctly, leaving the collapsee's siblings visible", function() {
460 // Collapse node "A".
461 tree
.getRootNode().childNodes
[0].collapse();
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);
469 describe("sortchange", function() {
470 it("should only fire a single sortchange event", function() {
471 var spy
= jasmine
.createSpy();
472 makeTree(testNodes
, {
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);
486 describe('reconfigure', function() {
487 beforeEach(function() {
488 makeTree(testNodes
, {
496 it('should preserve singleExpand:true', function() {
497 // Expand childNodes[0]
498 rootNode
.childNodes
[0].expand();
499 expect(rootNode
.childNodes
[0].isExpanded()).toBe(true);
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);
506 // Three root's childNodes plus the two child nodes of childNode[1]
507 expect(store
.getCount()).toBe(5);
509 // Identical Store to reconfigure with
510 var newStore
= new Ext
.data
.TreeStore({
521 tree
.reconfigure(newStore
);
522 rootNode
= newStore
.getRootNode();
524 // Back down to just the three root childNodes.
525 expect(newStore
.getCount()).toBe(3);
527 // Expand childNodes[0]
528 rootNode
.childNodes
[0].expand();
529 expect(rootNode
.childNodes
[0].isExpanded()).toBe(true);
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);
536 // Three root's childNodes plus the two child nodes of childNode[1]
537 expect(newStore
.getCount()).toBe(5);
541 describe('autoexpand collapsed ancestors', function() {
542 beforeEach(function() {
543 makeTree(testNodes
, {
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);
551 tree
.getStore().getNodeById('G').expand();
553 // "A" should be expanded all the way down to "H", then "I", then "M"
554 expect(store
.getCount()).toBe(9);
558 describe("removeAll", function() {
559 beforeEach(function(){
560 makeTree(testNodes
, {
564 it("should only refresh once when removeAll called", function() {
565 var nodeA
= tree
.getStore().getNodeById('A'),
568 expect(tree
.view
.refreshCounter
).toBe(1);
570 buffered
= view
.bufferedRenderer
&& view
.all
.getCount
>= view
.bufferedRenderer
.viewSize
;
572 // With all the nodes fully preloaded, a recursive expand
573 // should do one refresh.
574 expect(view
.refreshCounter
).toBe(2);
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.
583 expect(view
.refreshCounter
).toBe(buffered
? 3 : 2);
587 describe("Getting owner tree", function() {
588 beforeEach(function(){
591 it("should find the owner tree", function() {
592 var store
= tree
.getStore(),
593 h
= store
.getNodeById('H');
595 expect(h
.getOwnerTree()).toBe(tree
);
599 describe("updating row attributes", function() {
600 beforeEach(function(){
604 it("should set the data-qtip attribute", function() {
605 var rootRow
= tree
.view
.getRow(rootNode
),
606 rootCls
= rootRow
.className
;
608 rootNode
.set('qtip', 'Foo');
610 // Class should not change
611 expect(rootRow
.className
).toBe(rootCls
);
613 // data-qtip must be set
614 expect(rootRow
.getAttribute('data-qtip')).toBe('Foo');
617 it("should add the expanded class on expand", function() {
618 var view
= tree
.getView(),
619 cls
= view
.expandedCls
;
621 expect(view
.getRow(rootNode
)).not
.toHaveCls(cls
);
623 expect(view
.getRow(rootNode
)).toHaveCls(cls
);
626 it("should remove the expanded class on collapse", function() {
627 var view
= tree
.getView(),
628 cls
= view
.expandedCls
;
631 expect(view
.getRow(rootNode
)).toHaveCls(cls
);
633 expect(view
.getRow(rootNode
)).not
.toHaveCls(cls
);
637 describe("expandPath/selectPath", function(){
638 describe("expandPath", function(){
639 var expectedSuccess
, expectedNode
;
640 beforeEach(function() {
641 expectedSuccess
= false;
645 describe("callbacks", function(){
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
;
653 expect(expectedSuccess
).toBe(false);
654 expect(expectedNode
).toBeNull();
657 it("should default the scope to the tree", function(){
659 tree
.expandPath('', null, null, function(){
662 expect(scope
).toBe(tree
);
665 it("should use any specified scope", function(){
667 tree
.expandPath('', null, null, function(){
670 expect(scope
).toBe(o
);
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
;
680 expect(expectedSuccess
).toBe(false);
681 expect(expectedNode
).toBe(tree
.getRootNode());
684 it("should default the scope to the tree", function(){
686 tree
.expandPath('/NOTROOT', null, null, function(){
689 expect(scope
).toBe(tree
);
692 it("should use any specified scope", function(){
694 tree
.expandPath('/NOTROOT', null, null, function(){
697 expect(scope
).toBe(o
);
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
;
708 expect(expectedSuccess
).toBe(true);
709 expect(expectedNode
).toBe(tree
.getStore().getNodeById('B'));
710 expect(view
.all
.getCount()).toBe(9);
713 it("should default the scope to the tree", function() {
715 tree
.expandPath('/root/A/B', null, null, function(success
, lastExpanded
) {
718 expect(scope
).toBe(tree
);
721 it("should use any specified scope", function(){
723 tree
.expandPath('/root/A/B', null, null, function(success
, lastExpanded
) {
726 expect(scope
).toBe(o
);
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
;
734 expect(expectedSuccess
).toBe(true);
735 expect(expectedNode
).toBe(store
.getNodeById('G'));
736 expect(view
.all
.getCount()).toBe(9);
739 describe('New API', function() {
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
;
751 waitsFor(function() {
752 return expectedSuccess
;
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')));
762 it("should default the scope to the tree", function() {
764 tree
.expandPath('/root/A/B', {
765 callback: function(success
, lastExpanded
) {
769 waitsFor(function() {
770 return scope
=== tree
;
774 it("should use any specified scope", function(){
776 tree
.expandPath('/root/A/B', {
778 function(success
, lastExpanded
) {
783 waitsFor(function() {
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
;
795 waitsFor(function() {
796 return expectedSuccess
;
799 expect(expectedNode
).toBe(store
.getNodeById('G'));
800 expect(view
.all
.getCount()).toBe(9);
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
;
812 expect(expectedSuccess
).toBe(false);
813 expect(expectedNode
).toBe(tree
.getStore().getById('A'));
816 it("should default the scope to the tree", function(){
818 tree
.expandPath('/root/A/FAKE', null, null, function(){
821 expect(scope
).toBe(tree
);
824 it("should use any specified scope", function(){
826 tree
.expandPath('/root/A/FAKE', null, null, function(){
829 expect(scope
).toBe(o
);
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);
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);
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);
852 it("should accept a custom separator", function(){
853 tree
.expandPath('|root|A|B', null, '|');
854 expect(tree
.getStore().getById('B').isExpanded()).toBe(true);
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);
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
;
869 expect(expectedSuccess
).toBe(true);
870 expect(expectedNode
).toBe(tree
.getStore().getById('L'));
876 describe("selectPath", function(){
877 var isSelected = function(id
){
878 var node
= tree
.getStore().getById(id
);
879 return tree
.getSelectionModel().isSelected(node
);
882 var expectedSuccess
, expectedNode
;
883 beforeEach(function() {
884 expectedSuccess
= false;
888 describe("callbacks", function(){
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
;
897 expect(expectedSuccess
).toBe(false);
898 expect(expectedNode
).toBeNull();
901 it("should default the scope to the tree", function(){
903 tree
.selectPath('', null, null, function(){
906 expect(scope
).toBe(tree
);
909 it("should use any specified scope", function(){
911 tree
.selectPath('', null, null, function(){
914 expect(scope
).toBe(o
);
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
;
925 expect(expectedSuccess
).toBe(true);
926 expect(expectedNode
).toBe(tree
.getRootNode());
929 it("should default the scope to the tree", function(){
931 tree
.selectPath('/root', null, null, function(){
934 expect(scope
).toBe(tree
);
937 it("should use any specified scope", function(){
939 tree
.selectPath('/root', null, null, function(){
942 expect(scope
).toBe(o
);
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
;
953 expect(expectedSuccess
).toBe(true);
954 expect(expectedNode
).toBe(tree
.getStore().getById('B'));
957 it("should default the scope to the tree", function(){
959 tree
.selectPath('/root/A/B', null, null, function(){
962 expect(scope
).toBe(tree
);
965 it("should use any specified scope", function(){
967 tree
.selectPath('/root/A/B', null, null, function(){
970 expect(scope
).toBe(o
);
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
;
981 expect(expectedSuccess
).toBe(false);
982 expect(expectedNode
).toBe(tree
.getStore().getById('A'));
985 it("should default the scope to the tree", function(){
987 tree
.selectPath('/root/A/FAKE', null, null, function(){
990 expect(scope
).toBe(tree
);
993 it("should use any specified scope", function(){
995 tree
.selectPath('/root/A/FAKE', null, null, function(){
998 expect(scope
).toBe(o
);
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);
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);
1015 describe("custom separator", function(){
1016 it("should default the separator to /", function(){
1017 tree
.selectPath('/root/A');
1018 expect(isSelected('A')).toBe(true);
1021 it("should accept a custom separator", function(){
1022 tree
.selectPath('|root|A|B', null, '|');
1023 expect(isSelected('B')).toBe(true);
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);
1033 it("should select a leaf node", function(){
1034 tree
.selectPath('/root/I/L');
1035 expect(isSelected('L')).toBe(true);
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);
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'}]
1077 tree
.selectPath('2/3/4');
1078 expect(tree
.getSelectionModel().isSelected(store
.getNodeById(4)));
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',
1091 renderTo
: Ext
.getBody(),
1092 store
: store
= new Ext
.data
.TreeStore({
1095 secondaryId
: 'root',
1102 tree
.selectPath('/root/A/B/C');
1103 expect(tree
.getSelectionModel().isSelected(store
.getNodeById('C')));
1110 describe("expand/collapse", function(){
1111 var startingLayoutCounter
;
1113 beforeEach(function(){
1114 makeTree(testNodes
);
1115 startingLayoutCounter
= tree
.layoutCounter
;
1118 describe("expandAll", function(){
1120 describe("callbacks", function(){
1121 it("should pass the direct child nodes of the root", function(){
1124 store
= tree
.getStore();
1126 tree
.expandAll(function(nodes
) {
1127 expectedNodes
= nodes
;
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'));
1136 // Only one layout should have taken place
1137 expect(tree
.layoutCounter
).toBe(startingLayoutCounter
+ 1);
1140 it("should default the scope to the tree", function() {
1142 tree
.expandAll(function(){
1143 expectedScope
= this;
1145 expect(expectedScope
).toBe(tree
);
1148 it("should use a passed scope", function() {
1149 var o
= {}, expectedScope
;
1150 tree
.expandAll(function(){
1151 expectedScope
= this;
1153 expect(expectedScope
).toBe(o
);
1157 it("should expand all nodes", function(){
1159 Ext
.Array
.forEach(tree
.store
.getRange(), function(node
){
1160 if (!node
.isLeaf()) {
1161 expect(node
.isExpanded()).toBe(true);
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();
1171 Ext
.Array
.forEach(tree
.store
.getRange(), function(node
){
1172 if (!node
.isLeaf()) {
1173 expect(node
.isExpanded()).toBe(true);
1180 describe("collapseAll", function(){
1181 describe("callbacks", function(){
1183 it("should pass the direct child nodes of the root", function(){
1185 store
= tree
.getStore();
1187 tree
.collapseAll(function(nodes
) {
1188 expectedNodes
= nodes
;
1191 expect(expectedNodes
[0]).toBe(store
.getNodeById('A'));
1192 expect(expectedNodes
[1]).toBe(store
.getNodeById('I'));
1193 expect(expectedNodes
[2]).toBe(store
.getNodeById('M'));
1196 it("should default the scope to the tree", function() {
1198 tree
.collapseAll(function(){
1199 expectedScope
= this;
1201 expect(expectedScope
).toBe(tree
);
1204 it("should use a passed scope", function() {
1205 var o
= {}, expectedScope
;
1206 tree
.expandAll(function(){
1207 expectedScope
= this;
1209 expect(expectedScope
).toBe(o
);
1213 it("should collapse all nodes", function(){
1216 Ext
.Array
.forEach(tree
.store
.getRange(), function(node
){
1217 if (!node
.isLeaf()) {
1218 expect(node
.isExpanded()).toBe(false);
1223 it("should collapse all nodes all the way down the tree", function(){
1224 tree
.expandPath('/root/A/B/C');
1225 tree
.getRootNode().collapse();
1227 Ext
.Array
.forEach(tree
.store
.getRange(), function(node
){
1228 if (!node
.isLeaf()) {
1229 expect(node
.isExpanded()).toBe(false);
1235 describe("expand", function(){
1236 describe("callbacks", function(){
1237 it("should pass the nodes directly under the expanded node", function(){
1239 store
= tree
.getStore();
1241 tree
.expandNode(tree
.getRootNode(), false, function(nodes
){
1242 expectedNodes
= nodes
;
1245 expect(expectedNodes
[0]).toBe(store
.getNodeById('A'));
1246 expect(expectedNodes
[1]).toBe(store
.getNodeById('I'));
1247 expect(expectedNodes
[2]).toBe(store
.getNodeById('M'));
1250 it("should default the scope to the tree", function(){
1252 tree
.expandNode(tree
.getRootNode(), false, function(){
1253 expectedScope
= this;
1255 expect(expectedScope
).toBe(tree
);
1258 it("should use a passed scope", function(){
1259 var o
= {}, expectedScope
;
1260 tree
.expandNode(tree
.getRootNode(), false, function(){
1261 expectedScope
= this;
1263 expect(expectedScope
).toBe(o
);
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);
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);
1287 describe("collapse", function(){
1288 describe("callbacks", function(){
1289 it("should pass the nodes directly under the expanded node", function(){
1291 store
= tree
.getStore();
1293 tree
.collapseNode(tree
.getRootNode(), false, function(nodes
){
1294 expectedNodes
= nodes
;
1296 expect(expectedNodes
[0]).toBe(store
.getNodeById('A'));
1297 expect(expectedNodes
[1]).toBe(store
.getNodeById('I'));
1298 expect(expectedNodes
[2]).toBe(store
.getNodeById('M'));
1301 it("should default the scope to the tree", function(){
1303 tree
.collapseNode(tree
.getRootNode(), false, function(){
1304 expectedScope
= this;
1306 expect(expectedScope
).toBe(tree
);
1309 it("should use a passed scope", function(){
1310 var o
= {}, expectedScope
;
1311 tree
.collapseNode(tree
.getRootNode(), false, function(){
1312 expectedScope
= this;
1314 expect(expectedScope
).toBe(o
);
1318 describe("deep", function(){
1319 it("should only collapse a single level if deep is not specified", function(){
1320 var store
= tree
.getStore();
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);
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);
1341 describe("animations", function() {
1342 var enableFx
= Ext
.enableFx
;
1344 beforeEach(function() {
1345 makeTree = function(nodes
, cfg
) {
1348 renderTo
: Ext
.getBody(),
1349 store
: new Ext
.data
.TreeStore({
1352 secondaryId
: 'root',
1359 tree
= new Ext
.tree
.Panel(cfg
);
1363 afterEach(function() {
1364 Ext
.enableFx
= enableFx
;
1367 it("should enable animations when Ext.enableFx is true", function() {
1368 Ext
.enableFx
= true;
1372 expect(tree
.enableAnimations
).toBeTruthy();
1375 it("should disable animations when Ext.enableFx is false", function() {
1376 Ext
.enableFx
= false;
1380 expect(tree
.enableAnimations
).toBeFalsy();
1384 describe('event order', function() {
1385 it("should fire 'beforeitemexpand' before 'beforeload'", function() {
1387 beforeitemexpandOrder
,
1393 store
: new Ext
.data
.TreeStore({
1409 beforeitemexpand: function() {
1410 beforeitemexpandOrder
= order
;
1413 beforeload : function() {
1414 beforeloadOrder
= order
;
1422 layoutCounter
= tree
.layoutCounter
;
1423 tree
.getStore().getRoot().expand();
1425 Ext
.Ajax
.mockComplete({
1427 responseText
: Ext
.encode(testNodes
)
1430 // The order of events expected: beforeitemexpand, beforeload, load.
1431 expect(beforeitemexpandOrder
).toBe(0);
1432 expect(beforeloadOrder
).toBe(1);
1433 expect(loadOrder
).toBe(2);
1435 // The loading plus expand of the root should only have triggered one layout
1436 expect(tree
.layoutCounter
).toBe(layoutCounter
+ 1);
1440 describe("selected/focused/hover css classes", function() {
1441 var proto
= Ext
.view
.Table
.prototype,
1442 selectedItemCls
= proto
.selectedItemCls
,
1443 focusedItemCls
= proto
.focusedItemCls
,
1446 beforeEach(function() {
1447 makeTree(testNodes
, {
1450 selType
: 'rowmodel',
1454 tree
.getRootNode().expand();
1459 function blurActiveEl() {
1460 Ext
.getBody().focus();
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();
1468 expect(view
.getNodeByRecord(store
.getNodeById('A'))).toHaveCls(selectedItemCls
);
1469 expect(view
.getNodeByRecord(store
.getNodeById('M'))).toHaveCls(selectedItemCls
);
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
);
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();
1487 expect(view
.getNodeByRecord(store
.getNodeById('M'))).toHaveCls(selectedItemCls
);
1491 describe("renderer", function() {
1492 var CustomTreeColumnNoScope
= Ext
.define(null, {
1493 extend
: 'Ext.tree.Column',
1495 renderColText: function(v
) {
1496 return v
+ 'NoScope';
1498 renderer
: 'renderColText'
1500 CustomTreeColumnScopeThis
= Ext
.define(null, {
1501 extend
: 'Ext.tree.Column',
1503 renderColText: function(v
) {
1504 return v
+ 'ScopeThis';
1506 renderer
: 'renderColText',
1509 CustomTreeColumnScopeController
= Ext
.define(null, {
1510 extend
: 'Ext.tree.Column',
1513 TreeRendererTestController
= Ext
.define(null, {
1514 extend
: 'Ext.app.ViewController',
1515 renderColText: function(v
) {
1516 return v
+ 'ViewController';
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({
1524 renderTo
: Ext
.getBody(),
1525 store
: new Ext
.data
.TreeStore({
1532 columns
: [new CustomTreeColumnNoScope({
1537 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootNoScope');
1539 it("should be able to use a named renderer in the column with scope: 'this'", function() {
1540 tree
= new Ext
.tree
.Panel({
1542 renderTo
: Ext
.getBody(),
1543 store
: new Ext
.data
.TreeStore({
1550 columns
: [new CustomTreeColumnScopeThis({
1555 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootScopeThis');
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() {
1561 tree
= new Ext
.tree
.Panel({
1563 store
: new Ext
.data
.TreeStore({
1570 columns
: [new CustomTreeColumnScopeController({
1573 renderer
: 'renderColText',
1577 tree
.render(document
.body
);
1580 it("should be able to use a named renderer in a ViewController", function() {
1581 tree
= new Ext
.tree
.Panel({
1582 controller
: new TreeRendererTestController(),
1584 renderTo
: Ext
.getBody(),
1585 store
: new Ext
.data
.TreeStore({
1592 columns
: [new CustomTreeColumnNoScope({
1595 renderer
: 'renderColText'
1598 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
1601 tree
= new Ext
.tree
.Panel({
1602 controller
: new TreeRendererTestController(),
1604 renderTo
: Ext
.getBody(),
1605 store
: new Ext
.data
.TreeStore({
1612 columns
: [new CustomTreeColumnScopeController({
1615 renderer
: 'renderColText'
1618 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
1621 tree
= new Ext
.tree
.Panel({
1623 renderTo
: Ext
.getBody(),
1624 store
: new Ext
.data
.TreeStore({
1631 columns
: [new CustomTreeColumnNoScope({
1632 controller
: new TreeRendererTestController(),
1635 renderer
: 'renderColText',
1636 scope
: 'self.controller'
1639 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
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({
1644 renderTo
: Ext
.getBody(),
1645 store
: new Ext
.data
.TreeStore({
1652 columns
: [new CustomTreeColumnNoScope({
1653 defaultListenerScope
: true,
1656 renderColText: function(v
) {
1657 return v
+ 'ColDefaultScope';
1659 renderer
: 'renderColText'
1662 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootColDefaultScope');
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({
1667 renderTo
: Ext
.getBody(),
1668 store
: new Ext
.data
.TreeStore({
1675 defaultListenerScope
: true,
1676 panelRenderColText: function(v
) {
1677 return v
+ 'PanelDefaultScope';
1679 columns
: [new CustomTreeColumnNoScope({
1682 renderer
: 'panelRenderColText'
1685 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootPanelDefaultScope');
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({
1693 renderTo
: Ext
.getBody(),
1694 store
: new Ext
.data
.TreeStore({
1702 xtype
: 'treecolumn',
1705 renderColText: function(v
) {
1706 return v
+ 'NoScope';
1708 renderer
: 'renderColText'
1711 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootNoScope');
1713 it("should be able to use a named renderer in the column with scope: 'this'", function() {
1714 tree
= new Ext
.tree
.Panel({
1716 renderTo
: Ext
.getBody(),
1717 store
: new Ext
.data
.TreeStore({
1725 xtype
: 'treecolumn',
1728 renderColText: function(v
) {
1729 return v
+ 'ScopeThis';
1731 renderer
: 'renderColText',
1735 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootScopeThis');
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() {
1741 tree
= new Ext
.tree
.Panel({
1743 store
: new Ext
.data
.TreeStore({
1751 xtype
: 'treecolumn',
1754 renderColText: function(v
) {
1757 renderer
: 'renderColText',
1761 tree
.render(document
.body
);
1764 it("should be able to use a named renderer in a ViewController", function() {
1765 tree
= new Ext
.tree
.Panel({
1766 controller
: new TreeRendererTestController(),
1768 renderTo
: Ext
.getBody(),
1769 store
: new Ext
.data
.TreeStore({
1777 xtype
: 'treecolumn',
1780 renderer
: 'renderColText'
1783 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
1786 tree
= new Ext
.tree
.Panel({
1787 controller
: new TreeRendererTestController(),
1789 renderTo
: Ext
.getBody(),
1790 store
: new Ext
.data
.TreeStore({
1798 xtype
: 'treecolumn',
1801 renderer
: 'renderColText',
1805 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
1808 tree
= new Ext
.tree
.Panel({
1810 renderTo
: Ext
.getBody(),
1811 store
: new Ext
.data
.TreeStore({
1819 controller
: new TreeRendererTestController(),
1820 xtype
: 'treecolumn',
1823 renderer
: 'renderColText',
1824 scope
: 'self.controller'
1827 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootViewController');
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({
1832 renderTo
: Ext
.getBody(),
1833 store
: new Ext
.data
.TreeStore({
1841 xtype
: 'treecolumn',
1842 defaultListenerScope
: true,
1845 renderColText: function(v
) {
1846 return v
+ 'ColDefaultScope';
1848 renderer
: 'renderColText'
1851 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootColDefaultScope');
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({
1856 renderTo
: Ext
.getBody(),
1857 store
: new Ext
.data
.TreeStore({
1864 defaultListenerScope
: true,
1865 panelRenderColText: function(v
) {
1866 return v
+ 'PanelDefaultScope';
1869 xtype
: 'treecolumn',
1872 renderer
: 'panelRenderColText'
1875 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootPanelDefaultScope');
1879 it("should be able to use a renderer to render the value", function() {
1880 tree
= new Ext
.tree
.Panel({
1882 renderTo
: Ext
.getBody(),
1883 store
: new Ext
.data
.TreeStore({
1891 xtype
: 'treecolumn',
1894 renderer: function(v
) {
1899 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('RootFoo');
1902 it("should be able to use a string renderer that maps to Ext.util.Format", function () {
1903 tree
= new Ext
.tree
.Panel({
1905 renderTo
: Ext
.getBody(),
1906 store
: new Ext
.data
.TreeStore({
1914 xtype
: 'treecolumn',
1916 formatter
: 'uppercase',
1920 expect(tree
.el
.down('.x-tree-node-text').dom
.innerHTML
).toEqual('ROOT');
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'],
1935 afterEach(function () {
1936 Ext
.undefine('spec.Foo');
1937 Ext
.data
.Model
.schema
.clear(true);
1940 function getData() {
1942 "BaselineEndDate" : "2010-02-01",
1944 "Name" : "Planning",
1946 "StartDate" : "2010-01-18",
1947 "BaselineStartDate" : "2010-01-13",
1950 "TaskType" : "Important",
1952 "BaselineEndDate" : "2010-01-28",
1955 "Name" : "Investigate",
1957 "TaskType" : "LowPrio",
1958 "StartDate" : "2010-01-18",
1959 "BaselineStartDate" : "2010-01-20",
1962 "BaselineEndDate" : "2010-02-01",
1965 "Name" : "Assign resources",
1967 "StartDate" : "2010-01-18",
1968 "BaselineStartDate" : "2010-01-25",
1971 "BaselineEndDate" : "2010-02-01",
1974 "Name" : "Gather documents (not resizable)",
1975 "Resizable" : false,
1977 "StartDate" : "2010-01-18",
1978 "BaselineStartDate" : "2010-01-25",
1981 "BaselineEndDate" : "2010-02-04",
1984 "Name" : "Report to management",
1985 "TaskType" : "Important",
1987 "StartDate" : "2010-02-02",
1988 "BaselineStartDate" : "2010-02-04",
1994 it('should reload the root node', function() {
1995 var store
= new Ext
.data
.TreeStore({
1999 url
: '/data/AjaxProxy/treeLoadData'
2002 Name
: 'ROOOOOOOOT',
2007 tree
= new Ext
.tree
.Panel({
2008 renderTo
: Ext
.getBody(),
2016 xtype
: 'treecolumn',
2027 Ext
.Ajax
.mockComplete({
2029 responseText
: Ext
.encode(getData())
2032 var lockedView
= tree
.lockedGrid
.view
,
2033 normalView
= tree
.normalGrid
.view
;
2035 refreshSpy
= spyOnEvent(store
, 'refresh');
2038 Ext
.Ajax
.mockComplete({
2040 responseText
: Ext
.encode(getData())
2043 expect(refreshSpy
.callCount
).toBe(1);
2044 expect(lockedView
.getNodes().length
).toBe(6);
2045 expect(normalView
.getNodes().length
).toBe(6);
2049 describe('filtering', function() {
2059 text
: 'Second level 1',
2077 text
: 'Second level 2',
2095 text
: 'Second level 3',
2106 beforeEach(function() {
2107 makeTree(treeData
, {
2112 function testRowText(rowIdx
, value
) {
2113 return view
.store
.getAt(rowIdx
).get('text') === value
;
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();
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);
2128 // Filter so that only "foo" nodes and their ancestors are visible
2130 filterFn: function(node
) {
2131 var children
= node
.childNodes
,
2132 len
= children
&& children
.length
,
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,
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
++);
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);
2154 rootNode
.childNodes
[0].expand();
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);
2164 // Expand "Second level 1". It contains 1 "foo" child.
2165 rootNode
.childNodes
[0].childNodes
[2].expand();
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);
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
;
2180 // Now, with "Top 1" amd "Second level 1" already expanded, let's see only "bar" nodes and their ancestors.
2181 // View should refresh.
2183 filterFn: function(node
) {
2184 var children
= node
.childNodes
,
2185 len
= children
&& children
.length
,
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,
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
++);
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
);
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);
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();
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);
2228 // Collapse "Top 3". The "bar" and "Second level3" which contains a "bar" should disappear
2229 rootNode
.childNodes
[2].collapse();
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);
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);
2249 describe('sorting', function() {
2250 it('should sort nodes', function() {
2253 makeTree(testNodes
, null, {
2261 bNode
= tree
.store
.getNodeById('B');
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, {
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');
2275 // Sort using the owning TreeStore's sorter set.
2276 // It is by leaf status, then text, ASC.
2277 // These are all leaf nodes.
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');
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;
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');
2293 describe('Buffered rendering large, expanded root node', function() {
2294 function makeNodes() {
2300 for (i
= 0; i
< 50; i
++) {
2309 for (j
= 0; j
< 50; j
++) {
2311 node
.children
.push({
2312 id
: 'n' + ip1
+ '.' + jp1
,
2313 text
: 'Node' + ip1
+ '/' + jp1
,
2322 function completeWithNodes() {
2323 Ext
.Ajax
.mockComplete({
2325 responseText
: Ext
.encode(makeNodes())
2329 it('should maintain scroll position on reload', function() {
2336 url
: '/tree/Panel/load'
2345 completeWithNodes();
2347 view
.setScrollY(500);
2350 completeWithNodes();
2352 expect(view
.getScrollY()).toBe(500);
2355 it('should negate the animate flag and not throw an error', function() {
2363 url
: '/tree/Panel/load'
2371 completeWithNodes();
2373 // EXTJS-13673 buffered rendering should be turned on by default
2374 expect(tree
.view
.bufferedRenderer
instanceof Ext
.grid
.plugin
.BufferedRenderer
).toBe(true);
2377 it('should scroll to unloaded nodes by absolute path', function() {
2381 }, {// lazyFill means childNodes do not load locally available children arrays until expanded.
2385 url
: '/tree/Panel/load'
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.
2398 completeWithNodes();
2400 tree
.ensureVisible('/root/n50/n50.50');
2401 expect(Ext
.fly(view
.getNode(store
.getById('n50.50'))).getBox().bottom
).toBeLessThanOrEqual(view
.getBox().bottom
);
2404 it('should throw an error when being asked to scroll to an invisible root node', function() {
2410 // lazyFill means childNodes do not load locally available children arrays until expanded.
2414 url
: '/tree/Panel/load'
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.
2427 completeWithNodes();
2431 tree
.ensureVisible(rootNode
);
2432 }).toThrow('Unknown record passed to BufferedRenderer#scrollTo');
2436 it('should scroll to loaded nodes by relative path', function() {
2443 url
: '/tree/Panel/load'
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.
2456 completeWithNodes();
2459 tree
.ensureVisible('n50.50');
2460 expect(Ext
.fly(view
.getNode(store
.getById('n50.50'))).getBox().bottom
).toBeLessThanOrEqual(view
.getBox().bottom
);
2465 describe('multi append node', function() {
2469 beforeEach(function() {
2470 makeTree(testNodes
, null, null, {
2473 layoutCounter
= view
.componentLayoutCounter
;
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([{
2482 secondaryId
: 'append-1'
2486 secondaryId
: 'append-2'
2490 secondaryId
: 'append-3'
2494 secondaryId
: 'append-4'
2498 secondaryId
: 'append-5'
2502 expect(view
.all
.getCount()).toEqual(9);
2504 // We are shrinkwrap height, so it shuold have grown
2505 expect(tree
.getHeight()).toBeGreaterThan(height
);
2507 // All should have been done in one, rather than one update per node
2508 expect(view
.componentLayoutCounter
).toEqual(layoutCounter
+ 1);
2512 describe('tracking removed nodes', function() {
2513 it('should not add nodes removed by virtue of their parent collapsing to the removed list', function() {
2515 makeTree(testNodes
, null, {
2518 tree
.expandAll(function() {
2519 tree
.collapseAll(function() {
2523 waitsFor(function() {
2527 expect(tree
.store
.getRemovedRecords().length
).toBe(0);
2531 it('should add descendants of collapsed nodes to the removed list', function() {
2532 // Create tree with collapsed root node;
2533 makeTree(testNodes
, null, {
2537 tree
.store
.getRootNode().drop();
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);
2544 it('should add descendants of filtered out nodes to the removed list', function() {
2547 // Create tree with collapsed root node;
2548 makeTree(testNodes
, null, {
2551 tree
.expandAll(function() {
2554 waitsFor(function() {
2558 // When all are expanded, filter them all out.
2559 // Dropping the root node should still remove all descendants
2561 tree
.store
.filter('id', 'all_nodes_filtered_out');
2563 // Filtering should not add to remove list
2564 expect(tree
.store
.getRemovedRecords().length
).toBe(0);
2566 tree
.store
.getRootNode().drop();
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);
2574 describe('Changing root node', function() {
2575 it('should remove all listeners from old root node', function() {
2576 tree
= new Ext
.tree
.Panel({
2593 var oldRoot
= tree
.getRootNode();
2595 // The old root should have some listeners
2596 expect(Ext
.Object
.getKeys(oldRoot
.hasListeners
).length
).toBeGreaterThan(0);
2598 tree
.store
.setRoot({
2610 // The old root should have no listeners
2611 expect(Ext
.Object
.getKeys(oldRoot
.hasListeners
).length
).toBe(0);
2616 describe('sorting a collapsed node', function() {
2617 it('should not expand a collapsed node upon sort', function() {
2618 makeTree(testNodes
, null, {
2626 var aNode
= tree
.store
.getNodeById('A');
2628 // Sort the "A" node
2629 aNode
.sort(function(a
, b
) {
2630 return a
.get('text').localeCompare(b
.get('text'));
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);