]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | describe("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 |