]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/test/specs/grid/plugin/CellEditing.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / test / specs / grid / plugin / CellEditing.js
1 describe('Ext.grid.plugin.CellEditing', function () {
2 var store, plugin, grid, view, record, column, field,
3 TAB = 9,
4 synchronousLoad = true,
5 proxyStoreLoad = Ext.data.ProxyStore.prototype.load,
6 loadStore;
7
8 function makeGrid(pluginCfg, gridCfg, storeCfg, locked) {
9 store = new Ext.data.Store(Ext.apply({
10 fields: ['name', 'email', 'phone'],
11 data: [
12 {'name': 'Lisa', 'email': 'lisa@simpsons.com', 'phone': '555-111-1224', 'age': 14},
13 {'name': 'Bart', 'email': 'bart@simpsons.com', 'phone': '555-222-1234', 'age': 12},
14 {'name': 'Homer', 'email': 'homer@simpsons.com', 'phone': '555-222-1244', 'age': 44},
15 {'name': 'Marge', 'email': 'marge@simpsons.com', 'phone': '555-222-1254', 'age': 41}
16 ],
17 autoDestroy: true
18 }, storeCfg));
19
20 plugin = new Ext.grid.plugin.CellEditing(pluginCfg);
21
22 grid = new Ext.grid.Panel(Ext.apply({
23 columns: [
24 {header: 'Name', dataIndex: 'name', editor: 'textfield', locked: locked},
25 {header: 'Email', dataIndex: 'email', flex:1,
26 editor: {
27 xtype: 'textareafield',
28 allowBlank: false,
29 grow: true
30 }
31 },
32 {header: 'Phone', dataIndex: 'phone', editor: 'textfield'},
33 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
34 ],
35 store: store,
36 selModel: 'cellmodel',
37 plugins: [plugin],
38 width: 200,
39 height: 400,
40 renderTo: Ext.getBody()
41 }, gridCfg));
42
43 view = grid.view;
44 }
45
46 function startEdit(recId, colId) {
47 record = store.getAt(recId || 0);
48 column = grid.columns[colId || 0];
49 plugin.startEdit(record, column);
50 field = column.field;
51 }
52
53 function triggerEditorKey(target, key) {
54 // Ext.supports.SpecialKeyDownRepeat changes the event Ext.form.field.Base listens for!
55 jasmine.fireKeyEvent(target, Ext.supports.SpecialKeyDownRepeat ? 'keydown' : 'keypress', key);
56 }
57
58 beforeEach(function() {
59 // Override so that we can control asynchronous loading
60 loadStore = Ext.data.ProxyStore.prototype.load = function() {
61 proxyStoreLoad.apply(this, arguments);
62 if (synchronousLoad) {
63 this.flushLoad.apply(this, arguments);
64 }
65 return this;
66 };
67
68 MockAjaxManager.addMethods();
69 });
70
71 afterEach(function() {
72 // Undo the overrides.
73 Ext.data.ProxyStore.prototype.load = proxyStoreLoad;
74
75 tearDown();
76 MockAjaxManager.removeMethods();
77 });
78
79 function tearDown() {
80 store = plugin = grid = view = record = column = field = Ext.destroy(grid);
81 }
82
83 describe('finding the cell editing plugin in a locking grid', function() {
84 beforeEach(function() {
85 makeGrid({pluginId:'test-cell-editing'}, null, null, true);
86 });
87
88 it('should find it by id', function() {
89 expect(grid.getPlugin('test-cell-editing')).toBe(plugin);
90 });
91 it('should find it by ptype', function() {
92 expect(grid.findPlugin('cellediting')).toBe(plugin);
93 });
94 });
95
96 describe('effect of hiding columns on cell editing selection', function () {
97 // These specs show that hiding columns pre- or post- cell edit will not place the x-grid-cell-selected class on the wrong
98 // cell in the wrong column when the row is updated since Ext.view.Table:renderCell is now looking up the cell context by
99 // header rather than by index. See EXTJSIV-11653.
100 var wasEdited = false,
101 columnManager, cell;
102
103 beforeEach(function () {
104 makeGrid({
105 listeners: {
106 edit: function (editor) {
107 wasEdited = true;
108 }
109 }
110 }, {
111 selType: 'cellmodel'
112 });
113
114 columnManager = grid.getColumnManager();
115 });
116
117 afterEach(function () {
118 wasEdited = false;
119 columnManager = null;
120 });
121
122 it('should give the edited cell the selected class after initially hiding columns', function () {
123 // First hide the columns.
124 columnManager.getColumns()[0].hide();
125 columnManager.getColumns()[1].hide();
126
127 // Then do the edit.
128 record = grid.store.getAt(0);
129 column = columnManager.getColumns()[2];
130 cell = grid.view.getCell(record, column);
131
132 jasmine.fireMouseEvent(cell, 'dblclick');
133 plugin.getEditor(record, column).setValue('111-111-1111');
134 plugin.completeEdit();
135
136 waitsFor(function () {
137 return wasEdited;
138 });
139
140 runs(function () {
141 // Finally show that the selected cell is in the correct column.
142 cell = Ext.fly(grid.view.getNode(record)).down('.x-grid-cell-selected');
143 expect(cell.hasCls('x-grid-cell-' + column.id)).toBe(true);
144 });
145 });
146
147 it('should move the selected cell along with its column when other columns are hidden', function () {
148 record = grid.store.getAt(0);
149 column = columnManager.columns[2];
150 cell = grid.view.getCell(record, column);
151
152 jasmine.fireMouseEvent(cell, 'dblclick');
153 plugin.getEditor(record, column).setValue('111-111-1111');
154 plugin.completeEdit();
155
156 waitsFor(function () {
157 return wasEdited;
158 });
159
160 runs(function () {
161 // First simply show that the selected cell is in the correct column.
162 cell = Ext.fly(grid.view.getNode(record)).down('.x-grid-cell-selected');
163 expect(cell.hasCls('x-grid-cell-' + column.id)).toBe(true);
164
165 columnManager.columns[0].hide();
166
167 // Now show that the selected cell is still in the correct column.
168 cell = Ext.fly(grid.view.getNode(record)).down('.x-grid-cell-selected');
169 expect(cell.hasCls('x-grid-cell-' + column.id)).toBe(true);
170 });
171 });
172 });
173
174 describe('events', function () {
175 var editorContext, cancelEditFired;
176
177 afterEach(function () {
178 editorContext = null;
179 });
180
181 describe('beforeedit', function () {
182 it('should retain changes to the editing context in the event handler', function () {
183 // See EXTJSIV-11643.
184 makeGrid({
185 listeners: {
186 beforeedit: function (editor, context) {
187 context.value = 'motley';
188 editorContext = context;
189 }
190 }
191 });
192
193 startEdit();
194
195 expect(editorContext.value).toBe('motley');
196 });
197 });
198
199 describe('canceledit', function () {
200 beforeEach(function () {
201
202 // Must wait for async focus events from previous suite to complete.
203 waits(10);
204
205 runs(function() {
206 cancelEditFired = false;
207
208 makeGrid({
209 listeners: {
210 canceledit: function (editor, context) {
211 cancelEditFired = true;
212 editorContext = context;
213 }
214 }
215 });
216
217 startEdit();
218 });
219 });
220
221 it('should be able to get the original value when canceling the edit by the plugin', function() {
222 expect(plugin.editing).toBe(true);
223
224 // Note that the columnmove and columnresize events go through plugin.cancelEdit().
225 column.getEditor().setValue('baz');
226 plugin.cancelEdit();
227
228 expect(cancelEditFired).toBe(true);
229 expect(editorContext.originalValue).toBe('Lisa');
230 });
231
232 it('should be able to get the edited value when canceling the edit by the plugin', function() {
233 expect(plugin.editing).toBe(true);
234
235 // Note that the columnmove and columnresize events go through plugin.cancelEdit().
236 column.getEditor().setValue('foo');
237 plugin.cancelEdit();
238
239 expect(cancelEditFired).toBe(true);
240 expect(editorContext.value).toBe('foo');
241 });
242
243 it('should have different values for edited value and original value when canceling', function() {
244 expect(plugin.editing).toBe(true);
245
246 column.getEditor().setValue('foo');
247 plugin.cancelEdit();
248
249 expect(cancelEditFired).toBe(true);
250 expect(editorContext.value).not.toBe(editorContext.originalValue);
251 });
252
253 it('should be able to get the edited value when canceling the edit by the editor', function() {
254 expect(plugin.editing).toBe(true);
255
256 // Note that the canceledit event goes through editor.cancelEdit().
257 column.getEditor().setValue('bar');
258 plugin.getEditor(record, column).cancelEdit();
259
260 expect(cancelEditFired).toBe(true);
261 expect(editorContext.value).not.toBe(editorContext.originalValue);
262 expect(editorContext.value).toBe('bar');
263 });
264
265 describe('falsey values', function () {
266 it('should be able to capture falsey values when canceled by the plugin', function() {
267 expect(plugin.editing).toBe(true);
268
269 // Note that the columnmove and columnresize events go through plugin.cancelEdit().
270 column.getEditor().setValue('');
271 plugin.cancelEdit();
272
273 expect(cancelEditFired).toBe(true);
274 expect(editorContext.value).toBe('');
275 });
276
277 it('should be able to capture falsey values for the editedValue when canceled by the editor', function() {
278 expect(plugin.editing).toBe(true);
279
280 // Note that the canceledit event goes through editor.cancelEdit().
281 column.getEditor().setValue('');
282 plugin.getEditor(record, column).cancelEdit();
283
284 waitsFor(function() {
285 return cancelEditFired;
286 });
287 runs(function() {
288 expect(editorContext.value).toBe('');
289 });
290 });
291 });
292 });
293
294 describe('selecting ranges', function () {
295 // See EXTJS-16608.
296 var selModel;
297
298 function fireEvent(rowNum, eventName, shift) {
299 jasmine.fireMouseEvent(view.getNode(rowNum).getElementsByTagName('td')[0],eventName, null, null, null, !!shift);
300 }
301
302 function expectSelected(rec) {
303 var i, len;
304
305 if (arguments.length === 1) {
306 if (typeof rec == 'number') {
307 rec = store.getAt(rec);
308 }
309 expect(selModel.isSelected(rec)).toBe(true);
310 } else {
311 for (i = 0, len = arguments.length; i < len; ++i) {
312 expectSelected(arguments[i]);
313 }
314 }
315 }
316
317 afterEach(function () {
318 selModel = null;
319 });
320
321 function selectRange(eventName) {
322 describe('MULTI', function () {
323 beforeEach(function () {
324 makeGrid({
325 clicksToEdit: eventName === 'click' ? 1: 2
326 }, {
327 selModel: {
328 type: 'rowmodel',
329 mode: 'MULTI'
330 }
331 });
332
333 selModel = grid.selModel;
334 });
335
336 it('should select a range if we have a selection start point and shift is pressed', function () {
337 fireEvent(0, eventName);
338 fireEvent(3, eventName, true);
339 expectSelected(0, 1, 2, 3);
340 });
341
342 it('should maintain selection with a complex sequence', function() {
343 fireEvent(0, eventName);
344 expectSelected(0);
345 fireEvent(2, eventName, true);
346 expectSelected(0, 1, 2);
347 fireEvent(3, eventName);
348 expectSelected(3);
349 fireEvent(1, eventName, true);
350 expectSelected(1, 2, 3);
351
352 fireEvent(2, eventName);
353 expectSelected(2);
354 fireEvent(0, eventName, true);
355 expectSelected(0, 1, 2);
356 fireEvent(3, eventName, true);
357 expectSelected(2, 3);
358 });
359 });
360 }
361
362 selectRange('click');
363 selectRange('dblclick');
364 });
365 });
366
367 describe('sorting', function () {
368 it('should complete the edit when focusing the column', function () {
369 makeGrid();
370 startEdit();
371 column.focus();
372
373 expect(plugin.editing).toBe(false);
374 });
375 });
376
377 describe('making multiple selections with checkbox model', function () {
378 var store, selModel;
379
380 afterEach(function () {
381 store = selModel = null;
382 });
383
384 it('should keep existing selections when editing a cell in an previously-selected row', function () {
385 makeGrid(null, {
386 selModel: new Ext.selection.CheckboxModel({})
387 });
388
389 store = grid.store;
390 selModel = grid.selModel;
391
392 // Select all models in the store.
393 selModel.select(store.data.items);
394
395 // Now edit a cell.
396 startEdit(2);
397
398 // All the previous selections should still be selected.
399 expect(selModel.getSelection().length).toBe(store.data.length);
400 });
401
402 it('should expect that the correct records have been selected', function () {
403 var contains = Ext.Array.contains,
404 selections;
405
406 makeGrid(null, {
407 selModel: new Ext.selection.CheckboxModel({})
408 });
409
410 store = grid.store;
411 selModel = grid.selModel;
412
413 // Make some selections.
414 selModel.select([store.getAt(1), store.getAt(3)]);
415
416 // Now edit a cell in an unselected row.
417 // As of 5.0.1, it should NOT select, but should preserve existing MULTI selections: https://sencha.jira.com/browse/EXTJS-14472
418 startEdit();
419
420 selections = selModel.getSelection();
421
422 expect(contains(selections, store.getAt(0))).toBe(false);
423 expect(contains(selections, store.getAt(1))).toBe(true);
424 expect(contains(selections, store.getAt(2))).toBe(false);
425 expect(contains(selections, store.getAt(3))).toBe(true);
426 });
427
428 it('should keep existing selections when editing a cell in an unselected row', function () {
429 makeGrid(null, {
430 selModel: new Ext.selection.CheckboxModel({})
431 });
432
433 store = grid.store;
434 selModel = grid.selModel;
435
436 // Make some selections.
437 selModel.select([store.getAt(0), store.getAt(1)]);
438
439 // Now edit a cell in an unselected row.
440 // As of 5.0.1, it should NOT select, but should preserve existing MULTI selections: https://sencha.jira.com/browse/EXTJS-14472
441 startEdit(3, 0);
442
443 // The selections should now also include the row that contains the cell being edited.
444 expect(selModel.getSelection().length).toBe(2);
445 });
446 });
447
448 describe('setting value while remote querying', function () {
449 // These tests simulates a test case where a value is entered in the editor (either as .value or .rawValue) and then
450 // is tabbed out of the editor (and completing the edit) before the response returns and the combo store is loaded.
451 // See EXTJS-13127.
452 //
453 // There is a lot of coverage for combos, but we also needed to test the behavior of combos as cell editors. There have
454 // been bugs where raw values have been retained by the editor across tabs, i.e., if 'foo' is entered in the editor that
455 // same value will be retained as the user tabs through the grid (although this only seems to happen in grids where only
456 // a single column is editable, as tested below). Also, there have been bugs where the same editor value (.value) has been
457 // been written to each model as the user tabs (obviously not good). The following tests cover both of these scenarios.
458 //
459 // In addition, the tests cover what should happen if a value or raw value is set prior to or during the combo store load,
460 // both when forceSelection is on and off. In either case (of forceSelection), we have decided that the value should be
461 // allowed because the combo store hasn't been loaded yet. The contract with forceSelection is with the combo store, and if
462 // the user chooses to enter a value before said store is loaded then we cannot do anything about that as we cannot look
463 // anything up.
464
465 var comboStore, ed;
466
467 function createUI(forceSelection) {
468 comboStore = new Ext.data.Store({
469 fields: ['id', 'state', 'nickname'],
470 proxy: {
471 type: 'ajax',
472 url: 'fake',
473 reader: {
474 type: 'array'
475 }
476 }
477 });
478
479 makeGrid(null, {
480 columns: [{
481 header: 'State',
482 dataIndex: 'id',
483 renderer: function (value, metaData, record) {
484 return record.get('state');
485 },
486 editor: {
487 xtype: 'combo',
488 store: comboStore,
489 queryMode: 'remote',
490 typeAhead: true,
491 minChars: 2,
492 displayField: 'state',
493 valueField: 'id',
494 forceSelection: forceSelection
495 }
496 }]
497 }, {
498 fields: ['id', 'state', 'nickname'],
499 data: [
500 ['AL', 'Alabama', 'The Heart of Dixie'],
501 ['AK', 'Alaska', 'The Land of the Midnight Sun'],
502 ['AR', 'Arkansas', 'The Natural State'],
503 ['AZ', 'Arizona', 'The Grand Canyon State']
504 ],
505 proxy: {
506 type: 'memory',
507 reader: {
508 type: 'array'
509 }
510 }
511 });
512 }
513
514 describe('only one editable column', function () {
515 function initiateTests(expectation, loadStore) {
516 describe(expectation, function () {
517 function forceSelection(force) {
518 describe('forceSelection = ' + force, function () {
519 beforeEach(function () {
520 createUI(force);
521 });
522
523 afterEach(function () {
524 Ext.destroy(comboStore);
525 comboStore = ed = null;
526 });
527
528 function setup(force, method) {
529 // Initiate the edit.
530 jasmine.fireMouseEvent(grid.view.getNode(store.getAt(0)).getElementsByTagName('td')[0], 'dblclick');
531 ed = plugin.getActiveEditor();
532
533 if (loadStore) {
534 comboStore.load();
535 }
536
537
538 // Simulate the load which happens when text is typed into the editor.
539 // Let's then tab out to complete the edit.
540 if (method === 'setRawValue') {
541 ed.field.setRawValue('ben');
542 } else {
543 ed.setValue('ben');
544 }
545
546 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
547 }
548
549 function setValue(raw) {
550 var method = raw ? 'setRawValue' : 'setValue';
551
552 describe(method, function () {
553 it('should write the value to the model', function () {
554 var val = 'ben';
555
556 setup(force, method);
557
558 if (force && method === 'setRawValue') {
559 val = 'AL';
560 }
561
562 record = store.getAt(0);
563 expect(record.get('id')).toBe(val);
564 expect(record.get('state')).toBe('Alabama');
565 });
566
567 it('should not set any other fields in the model across tabs', function () {
568 // There have been bugs which caused the same value to be set in different models across tabs.
569 setup(force, method);
570
571 record = store.getAt(1);
572 expect(record.get('id')).toBe('AK');
573 expect(record.get('state')).toBe('Alaska');
574
575 record = store.getAt(2);
576 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
577 expect(record.get('state')).toBe('Arkansas');
578 expect(record.get('nickname')).toBe('The Natural State');
579
580 record = store.getAt(3);
581 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
582 expect(record.get('state')).toBe('Arizona');
583 expect(record.get('nickname')).toBe('The Grand Canyon State');
584 });
585
586 it('should give the editor different values across tabs', function () {
587 // There have been bugs which caused the editor to keep the same value across tabs.
588 setup(force, method);
589
590 // It should not propagate the user-inputted value.
591
592 // Let's make sure the editor has the correct value...
593 expect(ed.getValue()).toBe('AK');
594 expect(ed.field.getRawValue()).toBe('');
595
596 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
597 expect(ed.getValue()).toBe('AR');
598
599 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
600 expect(ed.getValue()).toBe('AZ');
601 });
602
603 it('should not give the editor a raw value because the combo store has not been loaded', function () {
604 // There have been bugs which caused the editor to keep the same raw value across tabs.
605 setup(force, method);
606
607 expect(ed.field.getRawValue()).toBe('');
608
609 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
610 expect(ed.field.getRawValue()).toBe('');
611
612 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
613 expect(ed.field.getRawValue()).toBe('');
614 });
615 });
616 }
617
618 setValue(false);
619 setValue(true);
620 });
621 }
622
623 forceSelection(false);
624 forceSelection(true);
625 });
626 }
627
628 initiateTests('before store load is initiated', false);
629 initiateTests('while store is loading', true);
630
631 describe('when tabbing (down/up to the contiguous row)', function () {
632 var activeEditor;
633
634 beforeEach(function () {
635 makeGrid({
636 clicksToEdit: 1
637 }, {
638 columns: [
639 {header: 'Name', dataIndex: 'name', editor: 'textfield'},
640 {header: 'Email', dataIndex: 'email', flex:1},
641 {header: 'Phone', dataIndex: 'phone'},
642 {header: 'Age', dataIndex: 'age'}
643 ],
644 selModel: 'rowmodel'
645 });
646
647 startEdit();
648
649 activeEditor = plugin.activeEditor;
650
651 // Spy on afterHide to count *successful* hides.
652 // hide may be called when already hidden during CellEditing tabbing sequence.
653 spyOn(activeEditor, 'afterHide').andCallThrough();
654
655 jasmine.fireKeyEvent(column.field.inputEl, 'keydown', 9);
656 });
657
658 afterEach(function () {
659 activeEditor = null;
660 });
661
662 it('should not complete', function () {
663 expect(activeEditor).not.toBe(null);
664 expect(plugin.activeColumn).not.toBe(null);
665 expect(plugin.activeRecord).not.toBe(null);
666 });
667
668 it('should hide the editor', function () {
669 expect(activeEditor).not.toBe(null);
670 expect(activeEditor.isVisible()).toBe(true);
671
672 // CellEditing is just part of actionable mode.
673 // Actionable mode does not know that you are going to focus to the same editor.
674 // It just desctivates the old row, activates the new row, and focuses the first tabbable element.
675 // Deactivating a row will hide the editors.
676 // So the "name" editor will have been hidden when that row was deactivated.
677 expect(activeEditor.afterHide.callCount).toBe(1);
678 });
679 });
680 });
681 });
682
683 describe('clicksToEdit', function () {
684 describe('2 clicks', function () {
685 beforeEach(function () {
686 makeGrid();
687 });
688
689 it('should default to 2', function () {
690 expect(plugin.clicksToEdit).toBe(2);
691 });
692
693 it('should begin editing when double-clicked', function () {
694 record = grid.store.getAt(0);
695 node = grid.view.getNodeByRecord(record);
696 jasmine.fireMouseEvent(Ext.fly(node).down('.x-grid-cell'), 'dblclick');
697
698 expect(plugin.activeEditor).not.toBeFalsy();
699 });
700
701 it('should not begin editing when single-clicked', function () {
702 record = grid.store.getAt(0);
703 node = grid.view.getNodeByRecord(record);
704 jasmine.fireMouseEvent(Ext.fly(node).down('.x-grid-cell'), 'click');
705
706 expect(plugin.activeEditor).toBeFalsy();
707 });
708
709 describe('editing a new cell', function () {
710 var cells, boundEl;
711
712 afterEach(function () {
713 cells = boundEl = null;
714 });
715
716 it('should update the activeEditor to point to the new cell, adjacent', function () {
717 record = grid.store.getAt(0);
718 node = grid.view.getNodeByRecord(record);
719 cells = Ext.fly(node).query('.x-grid-cell');
720
721 boundEl = cells[0];
722 jasmine.fireMouseEvent(boundEl, 'dblclick');
723
724 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
725
726 // Update the boundEl to our new cell.
727 boundEl = cells[1];
728 jasmine.fireMouseEvent(boundEl, 'dblclick');
729
730 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
731 });
732
733 it('should update the activeEditor to point to the new cell, below', function () {
734 record = grid.store.getAt(0);
735 node = grid.view.getNodeByRecord(record);
736 boundEl = Ext.fly(node).down('.x-grid-cell').dom;
737
738 jasmine.fireMouseEvent(boundEl, 'dblclick');
739
740 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
741
742 record = grid.store.getAt(1);
743 node = grid.view.getNodeByRecord(record);
744
745 // Update the boundEl to our new cell.
746 boundEl = Ext.fly(node).down('.x-grid-cell').dom;
747
748 jasmine.fireMouseEvent(boundEl, 'dblclick');
749
750 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
751 });
752 });
753 });
754
755 describe('1 click', function () {
756 beforeEach(function () {
757 makeGrid({
758 clicksToEdit: 1
759 });
760 });
761
762 it('should honor a different number than the default', function () {
763 expect(plugin.clicksToEdit).toBe(1);
764 });
765
766 it('should begin editing when single-clicked', function () {
767 record = grid.store.getAt(0);
768 node = grid.view.getNodeByRecord(record);
769 jasmine.fireMouseEvent(Ext.fly(node).down('.x-grid-cell'), 'click');
770
771 expect(plugin.activeEditor).not.toBeFalsy();
772 });
773
774 // Note: I'm disabling this for IE b/c certain versions (esp. 10 & 11) could not distinguish
775 // between single- and double-click.
776 if (!Ext.isIE) {
777 it('should not begin editing when double-clicked', function () {
778 record = grid.store.getAt(0);
779 node = grid.view.getNodeByRecord(record);
780 jasmine.fireMouseEvent(Ext.fly(node).down('.x-grid-cell'), 'dblclick');
781
782 expect(plugin.activeEditor).toBeFalsy();
783 });
784 }
785
786 describe('editing a new cell', function () {
787 var cells, boundEl;
788
789 afterEach(function () {
790 cells = boundEl = null;
791 });
792
793 it('should update the activeEditor to point to the new cell, adjacent', function () {
794 record = grid.store.getAt(0);
795 node = grid.view.getNodeByRecord(record);
796 cells = Ext.fly(node).query('.x-grid-cell');
797
798 boundEl = cells[0];
799 jasmine.fireMouseEvent(boundEl, 'click');
800
801 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
802
803 // Update the boundEl to our new cell.
804 boundEl = cells[1];
805 jasmine.fireMouseEvent(boundEl, 'click');
806
807 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
808 });
809
810 it('should update the activeEditor to point to the new cell, below', function () {
811 record = grid.store.getAt(0);
812 node = grid.view.getNodeByRecord(record);
813 boundEl = Ext.fly(node).down('.x-grid-cell').dom;
814
815 jasmine.fireMouseEvent(boundEl, 'click');
816
817 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
818
819 record = grid.store.getAt(1);
820 node = grid.view.getNodeByRecord(record);
821
822 // Update the boundEl to our new cell.
823 boundEl = Ext.fly(node).down('.x-grid-cell').dom;
824
825 jasmine.fireMouseEvent(boundEl, 'click');
826
827 expect(plugin.activeEditor.boundEl.dom).toBe(boundEl);
828 });
829 });
830 });
831 });
832
833 describe('the CellEditor', function () {
834 beforeEach(function () {
835 makeGrid();
836 startEdit();
837 });
838
839 it('should get an ownerCmp reference to the grid', function () {
840 waitsFor(function() {
841 return plugin.activeEditor && plugin.activeEditor.ownerCmp === grid;
842 });
843 });
844
845 it('should be able to lookup up its owner in the component hierarchy chain', function () {
846 waitsFor(function() {
847 return plugin.activeEditor && plugin.activeEditor.up('grid') === grid;
848 });
849 });
850
851 describe('positioning the editor', function () {
852 it('should default to "l-l!"', function () {
853 field = column.field;
854
855 expect(field.xtype).toBe('textfield');
856 waitsFor(function() {
857 return plugin.activeEditor && plugin.activeEditor.alignment === 'l-l!';
858 });
859 });
860
861 it('should constrain to the view if the editor goes out of bounds', function () {
862 // Wait for the beforeEach's startEdit to get started
863 waitsFor(function() {
864 return plugin.activeEditor && plugin.activeEditor.field.hasFocus;
865 }, 'editor to focus', 1000);
866
867 // Need to be able to correctly startEdit while editing to move edit location
868 runs(function() {
869 startEdit(0, 1);
870 });
871
872 waitsFor(function() {
873 return field.hasFocus && field.getRegion().top === Ext.fly(plugin.activeEditor.container).getRegion().top;
874 }, 'something funky to happen', 1000);
875 });
876
877 it('should not reposition when shown', function () {
878 plugin.completeEdit();
879
880 spyOn(Ext.AbstractComponent.prototype, 'setPosition');
881
882 startEdit(0, 1);
883
884 expect(plugin.activeEditor.setPosition).not.toHaveBeenCalled();
885 });
886
887 it('should not reposition when within a draggable container', function () {
888 // See EXTJS-15532.
889 var win;
890
891 tearDown();
892
893 makeGrid(null, {
894 renderTo: null
895 });
896
897 win = new Ext.window.Window({
898 items: grid
899 }).show();
900
901 startEdit();
902
903 spyOn(plugin.activeEditor, 'setPosition');
904
905 jasmine.fireMouseEvent(win.el.dom, 'mousedown');
906 jasmine.fireMouseEvent(win.el.dom, 'mousemove', win.x, win.y);
907 jasmine.fireMouseEvent(win.el.dom, 'mousemove', (win.x - 100), (win.y - 100));
908 jasmine.fireMouseEvent(win.el.dom, 'mouseup', 400);
909
910 expect(plugin.activeEditor.setPosition).not.toHaveBeenCalled();
911
912 win.destroy();
913 });
914 });
915
916 describe('as textfield', function () {
917 it('should start the edit when ENTER is pressed', function () {
918 var node = view.body.query('td', true)[0];
919
920 // Wait for the beforeEach's startEdit to take effect
921 waitsFor(function() {
922 return plugin.activeEditor && plugin.activeEditor.field.hasFocus;
923 }, 'beforeEach startEdit to take effect');
924
925 // First complete the edit (we start an edit in the top-level beforeEach).
926 runs(function() {
927 grid.setActionableMode(false);
928 });
929
930 // Wait for it to clear itself up and focus to return to the cell
931 waitsFor(function() {
932 return plugin.activeEditor == null && plugin.editing === false && Ext.Element.getActiveElement() === node;
933 }, 'actionable mode to end and cell to regain focus');
934
935 runs(function() {
936 jasmine.fireKeyEvent(node, 'keydown', 13);
937 });
938
939 waitsFor(function() {
940 return plugin.activeEditor && plugin.editing === true;
941 }, 'editing to start on the focused cell');
942 });
943
944 describe('when currently editing', function() {
945 it('should complete the edit when ENTER is pressed', function() {
946 var str = 'Utley is Top Dog',
947 model = store.getAt(0);
948
949 expect(model.get('name')).toBe('Lisa');
950 field.setValue(str);
951
952 jasmine.fireKeyEvent(field.inputEl.dom, 'keydown', 13);
953
954 waitsFor(function() {
955 return model.get('name') === str;
956 }, 'model to be set', 1000);
957
958 runs(function() {
959 expect(model.get('name')).toBe(str);
960 });
961 });
962
963 it('should cancel the edit when ESCAPE is pressed', function() {
964 jasmine.pressKey(field, 'esc');
965
966 waitsFor(function() {
967 return !plugin.editing;
968 }, 'editing to stop', 1000);
969
970 runs(function() {
971 expect(plugin.editing).toBe(false);
972 });
973 });
974 });
975 });
976
977 describe('as textarea', function () {
978 beforeEach(function () {
979 startEdit(0, 1);
980 });
981
982 it('should start the edit when ENTER is pressed', function () {
983 var node = view.body.query('td', true)[1];
984
985 // Wait for the beforeEach's startEdit to take effect
986 waitsFor(function() {
987 return plugin.activeEditor && plugin.activeEditor.field.hasFocus && view.actionableMode === true;
988 }, 'beforeEach startEdit to take effect');
989
990 // First complete the edit (we start an edit in the top-level beforeEach).
991 runs(function() {
992 grid.setActionableMode(false);
993 });
994
995 // Wait for it to clear itself up and focus to return to the cell
996 waitsFor(function() {
997 return plugin.activeEditor == null && plugin.editing === false && Ext.Element.getActiveElement() === node;
998 }, 'actionable mode to end and cell to regain focus');
999
1000 runs(function() {
1001 jasmine.fireKeyEvent(node, 'keydown', 13);
1002 });
1003
1004 waitsFor(function() {
1005 return plugin.activeEditor && plugin.editing === true;
1006 }, 'editing to start on the focused cell');
1007 });
1008
1009 describe('when currently editing', function () {
1010 it('should not complete the edit when ENTER is pressed', function () {
1011 spyOn(plugin, 'completeEdit');
1012
1013 // Wait for the beforeEach's startEdit to take effect
1014 waitsFor(function() {
1015 return plugin.activeEditor && plugin.activeEditor.field.hasFocus;
1016 }, 'beforeEach startEdit to take effect');
1017
1018 // First complete the edit (we start an edit in the top-level beforeEach).
1019 runs(function() {
1020 triggerEditorKey(field.inputEl, 13);
1021
1022 expect(plugin.completeEdit).not.toHaveBeenCalled();
1023 });
1024 });
1025
1026 it('should not cancel the edit when ENTER is pressed', function () {
1027 spyOn(plugin, 'cancelEdit');
1028
1029 // Wait for the beforeEach's startEdit to take effect
1030 waitsFor(function() {
1031 return plugin.activeEditor && plugin.activeEditor.field.hasFocus;
1032 }, 'beforeEach startEdit to take effect');
1033
1034 // First complete the edit (we start an edit in the top-level beforeEach).
1035 runs(function() {
1036 triggerEditorKey(field.inputEl, 13);
1037
1038 expect(plugin.cancelEdit).not.toHaveBeenCalled();
1039 });
1040 });
1041
1042 it('should cancel the edit when ESCAPE is pressed', function () {
1043 spyOn(plugin, 'cancelEdit');
1044
1045 // Wait for the beforeEach's startEdit to take effect
1046 waitsFor(function() {
1047 return plugin.activeEditor && plugin.activeEditor.field.hasFocus;
1048 }, 'beforeEach startEdit to take effect');
1049
1050 // First complete the edit (we start an edit in the top-level beforeEach).
1051 runs(function() {
1052 triggerEditorKey(field.inputEl, 27);
1053 });
1054
1055 waitsFor(function () {
1056 return !plugin.editing;
1057 }, 'ESC keydown to have terminated editing');
1058
1059 runs(function () {
1060 expect(plugin.editing).toBe(false);
1061 });
1062 });
1063
1064 describe('grow and auto-sizing', function () {
1065 var str = 'Attention all planets of the Solar Federation!\nAttention all planets of the Solar Federation!\nWe have assumed control!';
1066
1067 it('should auto-size when written to', function () {
1068 spyOn(field, 'autoSize');
1069
1070 field.setValue(str);
1071
1072 expect(field.autoSize).toHaveBeenCalled();
1073 });
1074
1075 it('should grow', function () {
1076 var previousHeight = field.getHeight();
1077
1078 field.setValue(str);
1079
1080 expect(field.getHeight()).toBeGreaterThan(previousHeight);
1081 });
1082 });
1083 });
1084 });
1085 });
1086
1087 describe('key mappings', function () {
1088 it('should not stop propagation on the enter key', function () {
1089 var EM = Ext.EventManager;
1090
1091 spyOn(EM, 'stopPropagation');
1092 spyOn(EM, 'preventDefault');
1093
1094 makeGrid();
1095 startEdit(0, 1);
1096
1097 triggerEditorKey(column.field.inputEl, 13);
1098
1099 expect(EM.stopPropagation).not.toHaveBeenCalled();
1100 expect(EM.preventDefault).not.toHaveBeenCalled();
1101 });
1102 });
1103
1104 describe('in a collapsed container', function () {
1105 // To reproduce the bug:
1106 // 1. Start edit
1107 // 2. Collapse the fieldset
1108 // 3. Create the new editor (or any component that contains an editor)
1109 // 4. Show the fieldset
1110 // 5. Try to start edit
1111 //
1112 // See EXTJS-12752.
1113 var fieldset, editor_1, editor_2;
1114
1115 beforeEach(function () {
1116 fieldset = new Ext.form.FieldSet({
1117 collapsible: true,
1118 items: makeGrid({
1119 renderTo: null
1120 }),
1121 width: 500,
1122 renderTo: Ext.getBody()
1123 });
1124
1125 startEdit();
1126 });
1127
1128 afterEach(function () {
1129 Ext.destroy(fieldset, editor_1, editor_2);
1130 fieldset = editor_1 = editor_2 = null;
1131 });
1132
1133 it('should not set its hierarchicallyHidden property in response to any hierarchyEvents', function () {
1134 waitsFor(function() {
1135 return (editor_1 = plugin.activeEditor) && editor_1.field.hasFocus;
1136 }, 'editing to start');
1137
1138 runs(function() {
1139
1140 // We have to fake a blur here.
1141 plugin.completeEdit();
1142
1143 fieldset.toggle();
1144
1145 editor_2 = new Ext.grid.CellEditor({
1146 field: 'textfield',
1147 renderTo: Ext.getBody()
1148 });
1149
1150 fieldset.toggle();
1151
1152 plugin.startEdit(record, column);
1153 });
1154
1155 waitsFor(function() {
1156 return editor_1.hidden === false;
1157 }, 'editor_1 to show');
1158
1159 runs(function() {
1160 expect(editor_1.hierarchicallyHidden).toBe(false);
1161 });
1162 });
1163
1164 it('should show the CellEditor when the edit is started', function () {
1165 waitsFor(function() {
1166 return (editor_1 = plugin.activeEditor) && editor_1.field.hasFocus;
1167 }, 'editing to start');
1168
1169 runs(function() {
1170
1171 // We have to fake a blur here.
1172 plugin.completeEdit();
1173
1174 fieldset.toggle();
1175
1176 editor_2 = new Ext.grid.CellEditor({
1177 field: 'textfield',
1178 renderTo: Ext.getBody()
1179 });
1180
1181 fieldset.toggle();
1182
1183 plugin.startEdit(record, column);
1184 });
1185
1186 waitsFor(function() {
1187 return editor_1.hidden === false;
1188 }, 'editor_1 to show');
1189 });
1190 });
1191
1192 describe('selectOnFocus', function () {
1193 // I could not get the following spec to pass in the following browsers, although the test case does work.
1194 // The dom.select() method in FF seems to be asynchronous (possibly for Opera as well), and IE 11 always
1195 // returned an empty string for the text selection even though it claims to support window.getSelection().
1196 ((Ext.isGecko || Ext.isOpera || Ext.isIE11) ? xit : it)('should select the text in the cell when initiating an edit', function () {
1197 // See EXTJS-12364.
1198 var node;
1199
1200 function getSelectionText() {
1201 var text;
1202
1203 if (!Ext.isIE) {
1204 text = window.getSelection().toString();
1205 } else if (document.selection) {
1206 text = document.selection.createRange().text;
1207 }
1208
1209 return text;
1210 }
1211
1212 makeGrid(null, {
1213 columns: [
1214 {header: 'Name', dataIndex: 'name',
1215 editor: {
1216 xtype: 'textfield',
1217 selectOnFocus: true
1218 }
1219 },
1220 {header: 'Email', dataIndex: 'email', flex:1,
1221 editor: {
1222 xtype: 'textfield',
1223 selectOnFocus: true
1224 }
1225 },
1226 {header: 'Phone', dataIndex: 'phone', editor: 'textfield'},
1227 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
1228 ]
1229 });
1230
1231 node = grid.view.getNode(grid.store.getAt(1));
1232 jasmine.fireMouseEvent(node.getElementsByTagName('td')[0], 'dblclick');
1233
1234 expect(getSelectionText()).toBe('Bart');
1235 });
1236 });
1237
1238 describe('not completing the edit', function () {
1239 beforeEach(function() {
1240
1241 // Must wait for async focus events from previous suite to complete.
1242 waits(10);
1243 });
1244 it('should preserve the correct editing context', function () {
1245 var listener = function () {
1246 return false;
1247 }, ed, context;
1248
1249 makeGrid(null, {
1250 columns: [
1251 {header: 'Name', dataIndex: 'name',
1252 editor: {
1253 xtype: 'textfield',
1254 selectOnFocus: true
1255 }
1256 },
1257 {header: 'Email', dataIndex: 'email', flex:1,
1258 editor: {
1259 xtype: 'textfield',
1260 selectOnFocus: true
1261 }
1262 },
1263 {header: 'Phone', dataIndex: 'phone', editor: 'textfield'},
1264 {header: 'Age', dataIndex: 'age', editor: 'textfield'}
1265 ]
1266 });
1267
1268 startEdit(0, 1);
1269 waitsFor(function() {
1270 ed = plugin.activeEditor;
1271 return !!ed;
1272 }, 'editing to start at cell(0, 1)');
1273 runs(function() {
1274 context = plugin.context;
1275
1276 ed.on('beforecomplete', listener);
1277 ed.setValue('derp');
1278 // Cancel edit.
1279 triggerEditorKey(ed.field.inputEl, 27);
1280
1281 expect(plugin.context).toBe(context);
1282 });
1283 });
1284 });
1285
1286 describe('operations that refresh the view', function () {
1287 var ed;
1288
1289 afterEach(function () {
1290 ed = null;
1291 });
1292
1293 describe('when editing and tabbing', function () {
1294 function doIt(autoSync) {
1295 it('should not complete the edit in the new position, autoSync ' + autoSync, function () {
1296 makeGrid(null, null, {
1297 autoSync: autoSync
1298 });
1299
1300 record = grid.store.getAt(0);
1301 column = grid.columns[0];
1302
1303 plugin.startEdit(record, column);
1304 ed = plugin.activeEditor;
1305 ed.setValue('Pete the Dog was here');
1306
1307 // Now let's tab and check that the editor is still shown and active.
1308 jasmine.fireKeyEvent(ed.field.inputEl, 'keydown', 9);
1309
1310 waitsFor(function () {
1311 return !!plugin.activeEditor.editing;
1312 }, 'editing to start', 1000);
1313
1314 runs(function () {
1315 // ed is the old editor
1316 expect(ed.editing).toBe(false);
1317 expect(plugin.activeEditor.editing).toBe(true);
1318 expect(plugin.activeColumn.dataIndex).toBe('email');
1319 });
1320 });
1321 }
1322
1323 doIt(true);
1324 doIt(false);
1325 });
1326
1327 describe('when editing and syncing', function () {
1328 it('should not complete the edit in the current position', function () {
1329 makeGrid();
1330
1331 record = grid.store.getAt(0);
1332 column = grid.columns[0];
1333
1334 plugin.startEdit(record, column);
1335 ed = plugin.activeEditor;
1336 ed.setValue('Pete the Dog was here');
1337 store.sync();
1338
1339 waitsFor(function () {
1340 return !!plugin.activeEditor.editing;
1341 }, 'editing to start', 1000);
1342
1343 runs(function () {
1344 expect(ed.editing).toBe(true);
1345 expect(plugin.activeColumn.dataIndex).toBe('name');
1346 });
1347 });
1348 });
1349 });
1350 });