export function sleep(delay = 100) { return Promise.resolve({ then: function(resolve) { setTimeout(resolve, delay); } }); }; export function hot() { return spec().$container.data('handsontable'); }; export function handsontable(options) { var currentSpec = spec(); currentSpec.$container.handsontable(options); currentSpec.$container[0].focus(); // otherwise TextEditor tests do not pass in IE8 return currentSpec.$container.data('handsontable'); }; /** * As for v. 0.11 the only scrolling method is native scroll, which creates copies of main htCore table inside of the container. * Therefore, simple $(".htCore") will return more than one object. Most of the time, you're interested in the original * htCore, not the copies made by native scroll. * * This method returns the original htCore object * * @returns {jqObject} reference to the original htCore */ export function getHtCore() { return spec().$container.find('.htCore').first(); }; export function getTopClone() { return spec().$container.find('.ht_clone_top'); }; export function getTopLeftClone() { return spec().$container.find('.ht_clone_top_left_corner'); }; // for compatybility // var getCornerClone = getTopLeftClone; export function getLeftClone() { return spec().$container.find('.ht_clone_left'); }; export function getBottomClone() { return spec().$container.find('.ht_clone_bottom'); }; export function getBottomLeftClone() { return spec().$container.find('.ht_clone_bottom_left_corner'); }; // Rename me to countTD export function countCells() { return getHtCore().find('tbody td').length; }; export function isEditorVisible() { return !!(keyProxy().is(':visible') && keyProxy().parent().is(':visible') && !keyProxy().parent().is('.htHidden')); }; export function isFillHandleVisible() { return !!spec().$container.find('.wtBorder.corner:visible').length; }; export function getCorrespondingOverlay(cell, container) { var overlay = $(cell).parents('.handsontable'); if (overlay[0] == container[0]) { return $('.ht_master'); } return $(overlay[0]); }; /** * Shows context menu */ export function contextMenu(cell) { var hot = spec().$container.data('handsontable'); var selected = hot.getSelected(); if (!selected) { hot.selectCell(0, 0); selected = hot.getSelected(); } if (!cell) { cell = getCell(selected[0], selected[1]); } var cellOffset = $(cell).offset(); $(cell).simulate('contextmenu', { clientX: cellOffset.left - Handsontable.dom.getWindowScrollLeft(), clientY: cellOffset.top - Handsontable.dom.getWindowScrollTop(), }); }; export function closeContextMenu() { $(document).simulate('mousedown'); // $(document).trigger('mousedown'); }; /** * Shows dropdown menu */ export function dropdownMenu(columnIndex) { var hot = spec().$container.data('handsontable'); var th = hot.view.wt.wtTable.getColumnHeader(columnIndex || 0); var button = th.querySelector('.changeType'); if (button) { $(button).simulate('mousedown'); $(button).simulate('click'); } }; export function closeDropdownMenu() { $(document).simulate('mousedown'); }; export function dropdownMenuRootElement() { var plugin = hot().getPlugin('dropdownMenu'); var root; if (plugin && plugin.menu) { root = plugin.menu.container; } return root; }; /** * Returns a function that triggers a mouse event * @param {String} type Event type * @return {Function} */ export function handsontableMouseTriggerFactory(type, button) { return function(element) { if (!(element instanceof jQuery)) { element = $(element); } var ev = $.Event(type); ev.which = button || 1; // left click by default element.simulate(type, ev); }; }; export const mouseDown = handsontableMouseTriggerFactory('mousedown'); export const mouseMove = handsontableMouseTriggerFactory('mousemove'); export const mouseOver = handsontableMouseTriggerFactory('mouseover'); export const mouseUp = handsontableMouseTriggerFactory('mouseup'); export function mouseDoubleClick(element) { mouseDown(element); mouseUp(element); mouseDown(element); mouseUp(element); }; export const mouseRightDown = handsontableMouseTriggerFactory('mousedown', 3); export const mouseRightUp = handsontableMouseTriggerFactory('mouseup', 3); /** * Returns a function that triggers a key event * @param {String} type Event type * @return {Function} */ export function handsontableKeyTriggerFactory(type) { return function(key, extend) { var ev = {}; // $.Event(type); if (typeof key === 'string') { if (key.indexOf('shift+') > -1) { key = key.substring(6); ev.shiftKey = true; } if (key.indexOf('ctrl+') > -1) { key = key.substring(5); ev.ctrlKey = true; ev.metaKey = true; } switch (key) { case 'tab': ev.keyCode = 9; break; case 'enter': ev.keyCode = 13; break; case 'esc': ev.keyCode = 27; break; case 'f2': ev.keyCode = 113; break; case 'arrow_left': ev.keyCode = 37; break; case 'arrow_up': ev.keyCode = 38; break; case 'arrow_right': ev.keyCode = 39; break; case 'arrow_down': ev.keyCode = 40; break; case 'ctrl': ev.keyCode = 17; break; case 'shift': ev.keyCode = 16; break; case 'backspace': ev.keyCode = 8; break; case 'delete': ev.keyCode = 46; break; case 'space': ev.keyCode = 32; break; case 'x': ev.keyCode = 88; break; case 'c': ev.keyCode = 67; break; case 'v': ev.keyCode = 86; break; default: throw new Error(`Unrecognised key name: ${key}`); } } else if (typeof key === 'number') { ev.keyCode = key; } // ev.originalEvent = {}; //needed as long Handsontable searches for event.originalEvent $.extend(ev, extend); $(document.activeElement).simulate(type, ev); }; }; export const keyDown = handsontableKeyTriggerFactory('keydown'); export const keyUp = handsontableKeyTriggerFactory('keyup'); /** * Presses keyDown, then keyUp */ export function keyDownUp(key, extend) { if (typeof key === 'string' && key.indexOf('shift+') > -1) { keyDown('shift'); } keyDown(key, extend); keyUp(key, extend); if (typeof key === 'string' && key.indexOf('shift+') > -1) { keyUp('shift'); } }; /** * Returns current value of the keyboard proxy textarea * @return {String} */ export function keyProxy() { return spec().$container.find('textarea.handsontableInput'); }; export function serveImmediatePropagation(event) { if (event != null && event.isImmediatePropagationEnabled == null) { event.stopImmediatePropagation = function() { this.isImmediatePropagationEnabled = false; this.cancelBubble = true; }; event.isImmediatePropagationEnabled = true; event.isImmediatePropagationStopped = function() { return !this.isImmediatePropagationEnabled; }; } return event; }; export function autocompleteEditor() { return spec().$container.find('.handsontableInput'); }; /** * Sets text cursor inside keyboard proxy */ export function setCaretPosition(pos) { var el = keyProxy()[0]; if (el.setSelectionRange) { el.focus(); el.setSelectionRange(pos, pos); } else if (el.createTextRange) { var range = el.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } }; /** * Returns autocomplete instance */ export function autocomplete() { return spec().$container.find('.autocompleteEditor'); }; /** * Triggers paste string on current selection */ export function triggerPaste(str) { spec().$container.data('handsontable').copyPaste.triggerPaste(null, str); }; /** * Calls a method in current Handsontable instance, returns its output * @param method * @return {Function} */ export function handsontableMethodFactory(method) { return function() { var instance; try { instance = spec().$container.handsontable('getInstance'); } catch (err) { console.error(err); } if (instance) { if (method === 'destroy') { spec().$container.removeData(); } } else { if (method === 'destroy') { return; // we can forgive this... maybe it was destroyed in the test } throw new Error('Something wrong with the test spec: Handsontable instance not found'); } return instance[method](...arguments); }; }; export const addHook = handsontableMethodFactory('addHook'); export const alter = handsontableMethodFactory('alter'); export const colToProp = handsontableMethodFactory('colToProp'); export const countCols = handsontableMethodFactory('countCols'); export const countRows = handsontableMethodFactory('countRows'); export const deselectCell = handsontableMethodFactory('deselectCell'); export const destroy = handsontableMethodFactory('destroy'); export const destroyEditor = handsontableMethodFactory('destroyEditor'); export const getActiveEditor = handsontableMethodFactory('getActiveEditor'); export const getCell = handsontableMethodFactory('getCell'); export const getCellEditor = handsontableMethodFactory('getCellEditor'); export const getCellMeta = handsontableMethodFactory('getCellMeta'); export const getCellMetaAtRow = handsontableMethodFactory('getCellMetaAtRow'); export const getCellRenderer = handsontableMethodFactory('getCellRenderer'); export const getCellsMeta = handsontableMethodFactory('getCellsMeta'); export const getCellValidator = handsontableMethodFactory('getCellValidator'); export const getColHeader = handsontableMethodFactory('getColHeader'); export const getCopyableData = handsontableMethodFactory('getCopyableData'); export const getCopyableText = handsontableMethodFactory('getCopyableText'); export const getData = handsontableMethodFactory('getData'); export const getDataAtCell = handsontableMethodFactory('getDataAtCell'); export const getDataAtCol = handsontableMethodFactory('getDataAtCol'); export const getDataAtRow = handsontableMethodFactory('getDataAtRow'); export const getDataAtRowProp = handsontableMethodFactory('getDataAtRowProp'); export const getDataType = handsontableMethodFactory('getDataType'); export const getInstance = handsontableMethodFactory('getInstance'); export const getRowHeader = handsontableMethodFactory('getRowHeader'); export const getSelected = handsontableMethodFactory('getSelected'); export const getSourceData = handsontableMethodFactory('getSourceData'); export const getSourceDataArray = handsontableMethodFactory('getSourceDataArray'); export const getSourceDataAtCell = handsontableMethodFactory('getSourceDataAtCell'); export const getSourceDataAtCol = handsontableMethodFactory('getSourceDataAtCol'); export const getSourceDataAtRow = handsontableMethodFactory('getSourceDataAtRow'); export const getValue = handsontableMethodFactory('getValue'); export const loadData = handsontableMethodFactory('loadData'); export const populateFromArray = handsontableMethodFactory('populateFromArray'); export const propToCol = handsontableMethodFactory('propToCol'); export const removeCellMeta = handsontableMethodFactory('removeCellMeta'); export const render = handsontableMethodFactory('render'); export const selectCell = handsontableMethodFactory('selectCell'); export const setCellMeta = handsontableMethodFactory('setCellMeta'); export const setDataAtCell = handsontableMethodFactory('setDataAtCell'); export const setDataAtRowProp = handsontableMethodFactory('setDataAtRowProp'); export const spliceCellsMeta = handsontableMethodFactory('spliceCellsMeta'); export const spliceCol = handsontableMethodFactory('spliceCol'); export const spliceRow = handsontableMethodFactory('spliceRow'); export const updateSettings = handsontableMethodFactory('updateSettings'); export const countSourceRows = handsontableMethodFactory('countSourceRows'); export const countSourceCols = handsontableMethodFactory('countSourceCols'); export const countEmptyRows = handsontableMethodFactory('countEmptyRows'); export const countEmptyCols = handsontableMethodFactory('countEmptyCols'); /** * Returns column width for HOT container * @param $elem * @param col * @returns {Number} */ export function colWidth($elem, col) { var TR = $elem[0].querySelector('TBODY TR'); var cell; if (TR) { cell = TR.querySelectorAll('TD')[col]; } else { cell = $elem[0].querySelector('THEAD TR').querySelectorAll('TH')[col]; } if (!cell) { throw new Error(`Cannot find table column of index '${col}'`); } return cell.offsetWidth; } /** * Returns row height for HOT container * @param $elem * @param row * @returns {Number} */ export function rowHeight($elem, row) { var TD; if (row >= 0) { TD = $elem[0].querySelector(`tbody tr:nth-child(${row + 1}) td`); } else { TD = $elem[0].querySelector(`thead tr:nth-child(${Math.abs(row)})`); } if (!TD) { throw new Error(`Cannot find table row of index '${row}'`); } return Handsontable.dom.outerHeight(TD); } /** * Returns value that has been rendered in table cell * @param {Number} trIndex * @param {Number} tdIndex * @returns {String} */ export function getRenderedValue(trIndex, tdIndex) { return spec().$container.find('tbody tr').eq(trIndex).find('td').eq(tdIndex).html(); } /** * Returns nodes that have been rendered in table cell * @param {Number} trIndex * @param {Number} tdIndex * @returns {String} */ export function getRenderedContent(trIndex, tdIndex) { return spec().$container.find('tbody tr').eq(trIndex).find('td').eq(tdIndex).children(); } /** * Create numerical data values for the table * @param rowCount * @param colCount * @returns {Array} */ export function createNumericData(rowCount, colCount) { rowCount = typeof rowCount === 'number' ? rowCount : 100; colCount = typeof colCount === 'number' ? colCount : 4; var rows = [], i, j; for (i = 0; i < rowCount; i++) { var row = []; for (j = 0; j < colCount; j++) { row.push((i + 1)); } rows.push(row); } return rows; } /** * Model factory, which creates object with private properties, accessible by setters and getters. * Created for the purpose of testing HOT with Backbone-like Models * @param opts * @returns {{}} * @constructor */ export function Model(opts) { var obj = {}; var _data = $.extend({ id: undefined, name: undefined, address: undefined }, opts); obj.attr = function(name, value) { if (typeof value === 'undefined') { return this.get(name); } return this.set(name, value); }; obj.get = function(name) { return _data[name]; }; obj.set = function(name, value) { _data[name] = value; return this; }; return obj; } /** * Factory which produces an accessor for objects of type "Model" (see above). * This function should be used to create accessor for a given property name and pass it as `data` option in column * configuration. * * @param name - name of the property for which an accessor function will be created * @returns {Function} */ export function createAccessorForProperty(name) { return function(obj, value) { return obj.attr(name, value); }; } export function resizeColumn(displayedColumnIndex, width) { var $container = spec().$container; var $th = $container.find(`thead tr:eq(0) th:eq(${displayedColumnIndex})`); $th.simulate('mouseover'); var $resizer = $container.find('.manualColumnResizer'); var resizerPosition = $resizer.position(); $resizer.simulate('mousedown', { clientX: resizerPosition.left, }); var delta = width - $th.width() - 2; var newPosition = resizerPosition.left + delta; $resizer.simulate('mousemove', { clientX: newPosition }); $resizer.simulate('mouseup'); } export function resizeRow(displayedRowIndex, height) { var $container = spec().$container; var $th = $container.find(`tbody tr:eq(${displayedRowIndex}) th:eq(0)`); $th.simulate('mouseover'); var $resizer = $container.find('.manualRowResizer'); var resizerPosition = $resizer.position(); $resizer.simulate('mousedown', { clientY: resizerPosition.top }); var delta = height - $th.height() - 2; if (delta < 0) { delta = 0; } $resizer.simulate('mousemove', { clientY: resizerPosition.top + delta }); $resizer.simulate('mouseup'); } export function moveSecondDisplayedRowBeforeFirstRow(container, secondDisplayedRowIndex) { var $mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first(), $rowHeaders = container.find('tbody tr th'), $firstRowHeader = $rowHeaders.eq(secondDisplayedRowIndex - 1), $secondRowHeader = $rowHeaders.eq(secondDisplayedRowIndex); $secondRowHeader.simulate('mouseover'); var $manualRowMover = $mainContainer.find('.manualRowMover'); if ($manualRowMover.length) { $manualRowMover.simulate('mousedown', { clientY: $manualRowMover[0].getBoundingClientRect().top }); $manualRowMover.simulate('mousemove', { clientY: $manualRowMover[0].getBoundingClientRect().top - 20 }); $firstRowHeader.simulate('mouseover'); $secondRowHeader.simulate('mouseup'); } } export function moveFirstDisplayedRowAfterSecondRow(container, firstDisplayedRowIndex) { var $mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first(), $rowHeaders = container.find('tbody tr th'), $firstRowHeader = $rowHeaders.eq(firstDisplayedRowIndex), $secondRowHeader = $rowHeaders.eq(firstDisplayedRowIndex + 1); $secondRowHeader.simulate('mouseover'); var $manualRowMover = $mainContainer.find('.manualRowMover'); if ($manualRowMover.length) { $manualRowMover.simulate('mousedown', { clientY: $manualRowMover[0].getBoundingClientRect().top }); $manualRowMover.simulate('mousemove', { clientY: $manualRowMover[0].getBoundingClientRect().top + 20 }); $firstRowHeader.simulate('mouseover'); $secondRowHeader.simulate('mouseup'); } } export function swapDisplayedColumns(container, from, to) { var $mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first(); var $colHeaders = container.find('thead tr:eq(0) th'); var $to = $colHeaders.eq(to); var $from = $colHeaders.eq(from); // Enter the second column header $from.simulate('mouseover'); var $manualColumnMover = $mainContainer.find('.manualColumnMover'); // Grab the second column $manualColumnMover.simulate('mousedown', { pageX: $manualColumnMover[0].getBoundingClientRect().left, }); // Drag the second column over the first column $manualColumnMover.simulate('mousemove', { pageX: $manualColumnMover[0].getBoundingClientRect().left - 20, }); $to.simulate('mouseover'); // Drop the second column $from.simulate('mouseup'); } export function triggerTouchEvent(type, target, pageX, pageY) { var e = document.createEvent('TouchEvent'); var targetCoords = target.getBoundingClientRect(); var touches; var targetTouches; var changedTouches; if (!pageX && !pageY) { pageX = parseInt(targetCoords.left + 3, 10); pageY = parseInt(targetCoords.top + 3, 10); } var touch = document.createTouch(window, target, 0, pageX, pageY, pageX, pageY); if (type == 'touchend') { touches = document.createTouchList(); targetTouches = document.createTouchList(); changedTouches = document.createTouchList(touch); } else { touches = document.createTouchList(touch); targetTouches = document.createTouchList(touch); changedTouches = document.createTouchList(touch); } e.initTouchEvent(type, true, true, window, null, 0, 0, 0, 0, false, false, false, false, touches, targetTouches, changedTouches, 1, 0); target.dispatchEvent(e); };