UNPKG

@quantlab/handsontable

Version:

Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs

1,493 lines (1,113 loc) 85.6 kB
'use strict'; describe('ContextMenu', function () { var id = 'testContainer'; beforeEach(function () { this.$container = $('<div id="' + id + '"></div>').appendTo('body'); }); afterEach(function () { if (this.$container) { destroy(); this.$container.remove(); } }); it('should update context menu items by calling `updateSettings` method', function () { var hot = handsontable({ contextMenu: ['row_above', 'row_below', '---------', 'remove_row'], height: 100 }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); var separators = items.filter('.htSeparator'); expect(actions.length).toEqual(3); expect(separators.length).toEqual(1); expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Remove row'].join('')); hot.updateSettings({ contextMenu: ['remove_row'] }); contextMenu(); items = $('.htContextMenu tbody td'); actions = items.not('.htSeparator'); separators = items.filter('.htSeparator'); expect(actions.length).toEqual(1); expect(separators.length).toEqual(0); expect(actions.text()).toEqual(['Remove row'].join('')); hot.updateSettings({ contextMenu: { items: { remove_col: true, hsep1: '---------', custom: { name: 'My custom item' } } } }); contextMenu(); items = $('.htContextMenu tbody td'); actions = items.not('.htSeparator'); separators = items.filter('.htSeparator'); expect(actions.length).toEqual(2); expect(separators.length).toEqual(1); expect(actions.text()).toEqual(['Remove column', 'My custom item'].join('')); }); describe('menu opening', function () { it('should open menu after right click on table cell', function () { var hot = handsontable({ contextMenu: true, height: 100 }); expect(hot.getPlugin('contextMenu')).toBeDefined(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should not open the menu after clicking an open editor', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); selectCell(2, 2); keyDownUp('enter'); expect(hot.getPlugin('contextMenu')).toBeDefined(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(hot.getActiveEditor().TEXTAREA); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should open menu after right click on header cell when only header cells are visible', function () { var hot = handsontable({ data: [], colHeaders: ['Year', 'Kia'], columns: [{ data: 0 }, { data: 1 }], contextMenu: true, height: 100 }); expect(hot.getPlugin('contextMenu')).toBeDefined(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(hot.rootElement.querySelector('.ht_clone_top thead th')); expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should open menu after right click on header corner', function () { var hot = handsontable({ data: [], colHeaders: true, rowHeaders: true, contextMenu: true, height: 100 }); expect(hot.getPlugin('contextMenu')).toBeDefined(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(hot.rootElement.querySelector('.ht_clone_top_left_corner thead th')); expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should open menu after right click active cell border', function () { var hot = handsontable({ contextMenu: true, height: 100 }); expect(hot.getPlugin('contextMenu')).toBeDefined(); expect($('.htContextMenu').is(':visible')).toBe(false); selectCell(0, 0); this.$container.find('.wtBorder.current:eq(0)').simulate('contextmenu'); expect($('.htContextMenu').is(':visible')).toBe(true); }); }); describe('menu closing', function () { it('should close menu after click', function () { var hot = handsontable({ contextMenu: true, height: 100 }); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); mouseDown(this.$container); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should close menu after click under the menu', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(500, 10), contextMenu: true, height: 500 }); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); var rect = $('.htContextMenu')[0].getBoundingClientRect(); var x = parseInt(rect.left + rect.width / 2, 10); var y = parseInt(rect.top + rect.height, 10); mouseDown(document.elementFromPoint(x, y)); expect($('.htContextMenu').is(':visible')).toBe(false); }); }); describe('menu disabled', function () { it('should not open menu after right click', function () { var hot = handsontable({ contextMenu: true, height: 100 }); hot.getPlugin('contextMenu').disablePlugin(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should not create context menu if it\'s disabled in constructor options', function () { var hot = handsontable({ contextMenu: false, height: 100 }); expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false); }); it('should reenable menu', function () { var hot = handsontable({ contextMenu: true, height: 100 }); hot.getPlugin('contextMenu').disablePlugin(); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(false); hot.getPlugin('contextMenu').enablePlugin(); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should reenable menu with updateSettings when it was disabled in constructor', function () { var hot = handsontable({ contextMenu: false, height: 100 }); expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false); updateSettings({ contextMenu: true }); expect(hot.getPlugin('contextMenu').isEnabled()).toBe(true); expect($('.htContextMenu').is(':visible')).toBe(false); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should disable menu with updateSettings when it was enabled in constructor', function () { var hot = handsontable({ contextMenu: true, height: 100 }); expect(hot.getPlugin('contextMenu').isEnabled()).toBe(true); updateSettings({ contextMenu: false }); expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false); }); it('should work properly (remove row) after destroy and new init', function () { var test = function test() { handsontable({ startRows: 5, contextMenu: ['remove_row'], height: 100 }); selectCell(0, 0); contextMenu(); $('.htContextMenu .ht_master .htCore tbody').find('td').not('.htSeparator').eq(0).simulate('mousedown'); expect(getData().length).toEqual(4); }; test(); destroy(); test(); }); }); describe('menu hidden items', function () { it('should remove separators from top, bottom and duplicated', function () { var hot = handsontable({ contextMenu: ['---------', '---------', 'row_above', '---------', '---------', 'row_below', '---------', 'remove_row'], height: 100 }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); var separators = items.filter('.htSeparator'); expect(actions.length).toEqual(3); expect(separators.length).toEqual(2); }); it('should hide option if hidden function return true', function () { var hot = handsontable({ startCols: 5, colHeaders: true, contextMenu: [{ key: '', name: 'Custom option', hidden: function hidden() { return !this.selection.selectedHeader.cols; } }] }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); expect(actions.length).toEqual(0); var header = $('.ht_clone_top thead th').eq(1); header.simulate('mousedown'); contextMenu(); items = $('.htContextMenu tbody td'); actions = items.not('.htSeparator'); expect(actions.length).toEqual(1); }); }); describe('menu destroy', function () { it('should close context menu when HOT is being destroyed', function () { var hot = handsontable({ contextMenu: true, height: 100 }); contextMenu(); expect($('.htContextMenu').is(':visible')).toBe(true); destroy(); expect($('.htContextMenu').is(':visible')).toBe(false); }); }); describe('subMenu', function () { it('should not open subMenu immediately', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); item.simulate('mouseover'); var contextSubMenu = $('.htContextMenuSub_' + item.text()).find('tbody td'); expect(contextSubMenu.length).toEqual(0); setTimeout(function () { var contextSubMenu = $('.htContextMenuSub_' + item.text()).find('tbody td'); expect(contextSubMenu.length).toEqual(0); done(); }, 100); }); it('should open subMenu with delay', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); item.simulate('mouseover'); setTimeout(function () { var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.length).toEqual(1); done(); }, 350); // menu opens after 300ms }); it('should NOT open subMenu if there is no subMenu for item', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8); item.simulate('mouseover'); expect(item.hasClass('htSubmenu')).toBe(false); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.length).toEqual(0); }); it('should open subMenu on the left of main menu if on the right there\'s no space left', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, Math.floor(window.innerWidth / 50)), contextMenu: true, width: window.innerWidth }); selectCell(0, countCols() - 1); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.offset().left).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll }); it('should open subMenu on the right of main menu if there\'s free space', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, Math.floor(window.innerWidth / 50)), contextMenu: true, width: window.innerWidth }); selectCell(0, countCols() - 9); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); setTimeout(function () { expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.offset().left).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll done(); }, 350); // menu opens after 300ms }); it('should open subMenu on the left-bottom of main menu if there\'s free space', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)), contextMenu: true, height: window.innerHeight }); window.scrollTo(0, document.body.clientHeight); selectCell(0, countCols() - 1); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); setTimeout(function () { expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(parseInt(contextSubMenu.offset().top, 10)).toBeAroundValue(parseInt(item.offset().top, 10) - 1); expect(parseInt(contextSubMenu.offset().left, 10)).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll done(); }, 350); // menu opens after 300ms }); it('should open subMenu on the right-bottom of main menu if there\'s free space', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)), contextMenu: true, height: window.innerHeight }); window.scrollTo(0, document.body.clientHeight); selectCell(0, countCols() - 9); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); setTimeout(function () { expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(parseInt(contextSubMenu.offset().top, 10)).toBeAroundValue(parseInt(item.offset().top, 10) - 1); expect(parseInt(contextSubMenu.offset().left, 10)).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll done(); }, 350); // menu opens after 300ms }); it('should open subMenu on the left-top of main menu if there\'s no free space on bottom', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)), contextMenu: true, height: window.innerHeight }); selectCell(countRows() - 1, countCols() - 1); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); setTimeout(function () { expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.offset().top + contextSubMenu.height() - 28).toBeAroundValue(item.offset().top); expect(contextSubMenu.offset().left).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll done(); }, 350); // menu opens after 300ms }); it('should open subMenu on the right-top of main menu if there\'s no free space on bottom', function (done) { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)), contextMenu: true, height: window.innerHeight }); selectCell(countRows() - 1, countCols() - 9); contextMenu(); var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9); var contextMenuRoot = $('.htContextMenu'); item.simulate('mouseover'); setTimeout(function () { expect(item.text()).toBe('Alignment'); expect(item.hasClass('htSubmenu')).toBe(true); var contextSubMenu = $('.htContextMenuSub_' + item.text()); expect(contextSubMenu.offset().top + contextSubMenu.height() - 28).toBeAroundValue(item.offset().top); expect(contextSubMenu.offset().left).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll done(); }, 350); // menu opens after 300ms }); }); describe('default context menu actions', function () { it('should display the default set of actions', function () { var hot = handsontable({ contextMenu: true, comments: true, height: 100 }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); var separators = items.filter('.htSeparator'); expect(actions.length).toEqual(15); expect(separators.length).toEqual(7); expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Insert column on the left', 'Insert column on the right', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment', 'Add comment', 'Delete comment', 'Read only comment', 'Copy', 'Cut'].join('')); }); it('should disable column manipulation when row header selected', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, colHeaders: true, rowHeaders: true, height: 100 }); $('.ht_clone_left .htCore').eq(0).find('tbody').find('th').eq(0).simulate('mousedown', { which: 3 }); contextMenu(); expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Insert column on the left', 'Insert column on the right', 'Remove column', 'Undo', 'Redo'].join('')); }); it('should disable row manipulation when column header selected', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, colHeaders: true, rowHeaders: true, height: 100 }); $('.ht_clone_top .htCore').find('thead').find('th').eq(2).simulate('mousedown', { which: 3 }); contextMenu(); expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Insert row above', 'Insert row below', 'Remove row', 'Undo', 'Redo'].join('')); }); it('should disable cells manipulation when corner header selected', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, colHeaders: true, rowHeaders: true, height: 100 }); $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0).simulate('mousedown', { which: 3 }); contextMenu(); expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment'].join('')); }); it('should insert row above selection', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 400 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(4); selectCell(1, 0, 3, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above expect(afterCreateRowCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined); expect(countRows()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert row above selection when initial data is empty', function () { var hot = handsontable({ rowHeaders: true, colHeaders: true, data: [], dataSchema: [], contextMenu: true, height: 400 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(0); var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0); cell.simulate('mousedown', { which: 3 }); contextMenu(cell[0]); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above expect(afterCreateRowCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined); expect(countRows()).toEqual(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should NOT display insert row selection', function () { var hot = handsontable({ contextMenu: true, allowInsertRow: false }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); var separators = items.filter('.htSeparator'); expect(actions.length).toEqual(10); expect(separators.length).toEqual(5); expect(actions.text()).toEqual(['Insert column on the left', 'Insert column on the right', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment', 'Copy', 'Cut'].join('')); }); it('should NOT display insert column selection', function () { var hot = handsontable({ contextMenu: true, allowInsertColumn: false }); contextMenu(); var items = $('.htContextMenu tbody td'); var actions = items.not('.htSeparator'); expect(actions.length).toEqual(10); expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment', 'Copy', 'Cut'].join('')); }); it('should insert row above selection (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(4); selectCell(3, 0, 1, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above expect(afterCreateRowCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined); expect(countRows()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert row below selection', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(4); selectCell(1, 0, 3, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row above expect(afterCreateRowCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined); expect(countRows()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert row below selection when initial data is empty', function () { var hot = handsontable({ rowHeaders: true, colHeaders: true, data: [], dataSchema: [], contextMenu: true, height: 400 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(0); var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0); cell.simulate('mousedown', { which: 3 }); contextMenu(cell[0]); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row below expect(afterCreateRowCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined); expect(countRows()).toEqual(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert row below selection (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback'); hot.addHook('afterCreateRow', afterCreateRowCallback); expect(countRows()).toEqual(4); selectCell(3, 0, 1, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row below expect(afterCreateRowCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined); expect(countRows()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the left of selection', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, width: 400, height: 400 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(4); selectCell(0, 1, 0, 3); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col left expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined); expect(countCols()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the left of selection when initial data is empty', function () { var hot = handsontable({ rowHeaders: true, colHeaders: true, data: [], dataSchema: [], contextMenu: true, height: 400 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(0); var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0); cell.simulate('mousedown', { which: 3 }); contextMenu(cell[0]); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert column on the left expect(afterCreateColCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.columnRight', undefined, undefined, undefined); expect(countCols()).toEqual(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the left of selection (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(4); selectCell(0, 3, 0, 1); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col left expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined); expect(countCols()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the right of selection', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(4); selectCell(0, 1, 0, 3); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col right expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined); expect(countCols()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the right of selection when initial data is empty', function () { var hot = handsontable({ rowHeaders: true, colHeaders: true, data: [], dataSchema: [], contextMenu: true, height: 400 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(0); var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0); cell.simulate('mousedown', { which: 3 }); contextMenu(cell[0]); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert column on the right expect(afterCreateColCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.columnRight', undefined, undefined, undefined); expect(countCols()).toEqual(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should insert column on the right of selection (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback'); hot.addHook('afterCreateCol', afterCreateColCallback); expect(countCols()).toEqual(4); selectCell(0, 3, 0, 1); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert col right expect(afterCreateColCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.columnRight', undefined, undefined, undefined); expect(countCols()).toEqual(5); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should remove selected rows', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback'); hot.addHook('afterRemoveRow', afterRemoveRowCallback); expect(countRows()).toEqual(4); selectCell(1, 0, 3, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row expect(afterRemoveRowCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeRow', undefined, undefined); expect(countRows()).toEqual(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should allow to remove the latest row', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(1, 4), contextMenu: true, height: 100 }); var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback'); hot.addHook('afterRemoveRow', afterRemoveRowCallback); expect(countRows()).toBe(1); selectCell(0, 0, 0, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row expect(afterRemoveRowCallback).toHaveBeenCalledWith(0, 1, [0], 'ContextMenu.removeRow', undefined, undefined); expect(countRows()).toBe(0); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should remove selected rows (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback'); hot.addHook('afterRemoveRow', afterRemoveRowCallback); expect(countRows()).toBe(4); selectCell(3, 0, 1, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row expect(afterRemoveRowCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeRow', undefined, undefined); expect(countRows()).toBe(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should remove selected columns', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback'); hot.addHook('afterRemoveCol', afterRemoveColCallback); expect(countCols()).toBe(4); selectCell(0, 1, 0, 3); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove col expect(afterRemoveColCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeColumn', undefined, undefined); expect(countCols()).toBe(1); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should allow to remove the latest column', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 1), contextMenu: true, height: 100 }); var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback'); hot.addHook('afterRemoveCol', afterRemoveColCallback); expect(countCols()).toBe(1); selectCell(0, 0, 0, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove column expect(afterRemoveColCallback).toHaveBeenCalledWith(0, 1, [0], 'ContextMenu.removeColumn', undefined, undefined); expect(countCols()).toBe(0); expect($('.htContextMenu').is(':visible')).toBe(false); }); it('should remove selected columns (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback'); hot.addHook('afterRemoveCol', afterRemoveColCallback); expect(countCols()).toEqual(4); selectCell(0, 3, 0, 1); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove col expect(afterRemoveColCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeColumn', undefined, undefined); expect(countCols()).toEqual(1); }); it('should undo changes', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); selectCell(0, 0); expect(getDataAtCell(0, 0)).toEqual('A1'); setDataAtCell(0, 0, 'XX'); expect(getDataAtCell(0, 0)).toEqual('XX'); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(6).simulate('mousedown'); // Undo expect(getDataAtCell(0, 0)).toEqual('A1'); }); it('should redo changes', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); selectCell(0, 0); expect(getDataAtCell(0, 0)).toEqual('A1'); setDataAtCell(0, 0, 'XX'); expect(getDataAtCell(0, 0)).toEqual('XX'); hot.undo(); expect(getDataAtCell(0, 0)).toEqual('A1'); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(7).simulate('mousedown'); // Redo expect(getDataAtCell(0, 0)).toEqual('XX'); }); it('should display only the specified actions', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: ['remove_row', 'undo'], height: 100 }); contextMenu(); expect($('.htContextMenu .ht_master .htCore').find('tbody td').length).toEqual(2); }); it('should make a single selected cell read-only', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); selectCell(0, 0); expect(getDataAtCell(0, 0)).toEqual('A1'); expect(hot.getCellMeta(0, 0).readOnly).toBe(false); selectCell(0, 0); contextMenu(); var menu = $('.htContextMenu .ht_master .htCore tbody'); menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); // Make read-only expect(hot.getCellMeta(0, 0).readOnly).toBe(true); }); it('should make a single selected cell writable, when it\'s set to read-only', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); selectCell(0, 0); expect(getDataAtCell(0, 0)).toEqual('A1'); hot.getCellMeta(0, 0).readOnly = true; selectCell(0, 0); contextMenu(); var menu = $('.htContextMenu .ht_master .htCore tbody'); menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); expect(hot.getCellMeta(0, 0).readOnly).toBe(false); }); it('should make a group of selected cells read-only, if all of them are writable', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { expect(hot.getCellMeta(i, j).readOnly).toEqual(false); } } selectCell(0, 0, 2, 2); contextMenu(); var menu = $('.htContextMenu .ht_master .htCore tbody'); menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); for (var _i = 0; _i < 2; _i++) { for (var _j = 0; _j < 2; _j++) { expect(hot.getCellMeta(_i, _j).readOnly).toEqual(true); } } }); it('should not close menu after clicking on submenu root item', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: ['row_above', 'remove_row', '---------', 'alignment'], height: 400 }); selectCell(1, 0, 3, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Alignment expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should make a group of selected cells read-only, if all of them are writable (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { expect(hot.getCellMeta(i, j).readOnly).toEqual(false); } } selectCell(2, 2, 0, 0); contextMenu(); var menu = $('.htContextMenu .ht_master .htCore tbody'); menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); // Make read-only for (var _i2 = 0; _i2 < 2; _i2++) { for (var _j2 = 0; _j2 < 2; _j2++) { expect(hot.getCellMeta(_i2, _j2).readOnly).toEqual(true); } } }); it('should make a group of selected cells writable if at least one of them is read-only', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { expect(hot.getCellMeta(i, j).readOnly).toEqual(false); } } hot.getCellMeta(1, 1).readOnly = true; selectCell(0, 0, 2, 2); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8).simulate('mousedown'); // Make writable for (var _i3 = 0; _i3 < 2; _i3++) { for (var _j3 = 0; _j3 < 2; _j3++) { expect(hot.getCellMeta(_i3, _j3).readOnly).toEqual(false); } } }); it('should make a group of selected cells writable if at least one of them is read-only (reverse selection)', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: true, height: 100 }); for (var i = 0; i < 2; i++) { for (var j = 0; j < 2; j++) { expect(hot.getCellMeta(i, j).readOnly).toEqual(false); } } hot.getCellMeta(1, 1).readOnly = true; selectCell(2, 2, 0, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8).simulate('mousedown'); // Make writable for (var _i4 = 0; _i4 < 2; _i4++) { for (var _j4 = 0; _j4 < 2; _j4++) { expect(hot.getCellMeta(_i4, _j4).readOnly).toEqual(false); } } }); }); describe('disabling actions', function () { it('should not close menu after clicking on disabled item', function () { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(4, 4), contextMenu: ['undo', 'redo'], height: 400 }); selectCell(1, 0, 3, 0); contextMenu(); $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Undo expect($('.htContextMenu').is(':visible')).toBe(true); }); it('should disable undo and redo action if undoRedo plugin is not enabled ', function () { var hot = handsontable({ contextMenu: true, undoRedo: false, height: 100 }); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(9)').text()).toEqual('Undo'); expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(true); expect($menu.find('tbody td:eq(10)').text()).toEqual('Redo'); expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(true); }); it('should disable undo when there is nothing to undo ', function () { var hot = handsontable({ contextMenu: true, height: 100 }); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect(hot.undoRedo.isUndoAvailable()).toBe(false); expect($menu.find('tbody td:eq(9)').text()).toEqual('Undo'); expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(true); closeContextMenu(); setDataAtCell(0, 0, 'foo'); contextMenu(); $menu = $('.htContextMenu .ht_master .htCore'); expect(hot.undoRedo.isUndoAvailable()).toBe(true); expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(false); }); it('should disable redo when there is nothing to redo ', function () { var hot = handsontable({ contextMenu: true, height: 100 }); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect(hot.undoRedo.isRedoAvailable()).toBe(false); expect($menu.find('tbody td:eq(10)').text()).toEqual('Redo'); expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(true); closeContextMenu(); setDataAtCell(0, 0, 'foo'); hot.undo(); contextMenu(); $menu = $('.htContextMenu .ht_master .htCore'); expect(hot.undoRedo.isRedoAvailable()).toBe(true); expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(false); }); it('should disable Insert row in context menu when maxRows is reached', function () { var hot = handsontable({ contextMenu: true, maxRows: 6, height: 100 }); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(0)').text()).toEqual('Insert row above'); expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(false); expect($menu.find('tbody td:eq(1)').text()).toEqual('Insert row below'); expect($menu.find('tbody td:eq(1)').hasClass('htDisabled')).toBe(false); closeContextMenu(); alter('insert_row'); contextMenu(); $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(true); expect($menu.find('tbody td:eq(1)').hasClass('htDisabled')).toBe(true); }); it('should disable Insert col in context menu when maxCols is reached', function () { var hot = handsontable({ contextMenu: true, maxCols: 6, height: 100 }); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(3)').text()).toEqual('Insert column on the left'); expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(false); expect($menu.find('tbody td:eq(4)').text()).toEqual('Insert column on the right'); expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(false); closeContextMenu(); alter('insert_col'); contextMenu(); $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(true); expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(true); }); it('should NOT disable Insert col in context menu when only one column exists', function () { var hot = handsontable({ data: [['single col']], contextMenu: true, maxCols: 10, height: 100 }); selectCell(0, 0); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(3)').text()).toEqual('Insert column on the left'); expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(false); expect($menu.find('tbody td:eq(4)').text()).toEqual('Insert column on the right'); expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(false); }); it('should disable Remove col in context menu when rows are selected by headers', function () { var hot = handsontable({ contextMenu: ['remove_col', 'remove_row'], height: 100, colHeaders: true, rowHeaders: true }); var $rowsHeaders = this.$container.find('.ht_clone_left tr th'); $rowsHeaders.eq(1).simulate('mousedown'); $rowsHeaders.eq(2).simulate('mouseover'); $rowsHeaders.eq(3).simulate('mouseover'); $rowsHeaders.eq(3).simulate('mousemove'); $rowsHeaders.eq(3).simulate('mouseup'); contextMenu(); var $menu = $('.htContextMenu .ht_master .htCore'); expect($menu.find('tbody td:eq(0)').text()).toEqual('Remove column'); expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(true); }); it('should disable Remove row in context menu when columns are selected by headers', function () { var hot = handsontable({ contextMenu: ['remove_col', 'remove_row'], height: 100, colHeaders: true, rowHeaders: true }); this.$container.find('thead tr:eq(0) th:eq(1)').simulate('mousedown'); this.$container.find('thead tr:eq(0) th:eq(2)').simulate('mouseover'); this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mouseover'); this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mousemove'); this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mouseup'); context