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