diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py index ce6fa972..47c7221f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py @@ -45,7 +45,7 @@ class TestColumnPropertiesSql(SQLTemplateTestBase): self.assertEqual('some_column', first_row['name']) self.assertEqual('character varying', first_row['cltype']) - self.assertEqual(2, len(fetch_result)) + self.assertEqual(3, len(fetch_result)) @staticmethod def get_template_file(version, filename): diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py index 338e5f71..08fc5556 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py @@ -11,6 +11,7 @@ import pyperclip import time from selenium.webdriver import ActionChains +from selenium.webdriver.common.keys import Keys from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest @@ -21,9 +22,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): Tests various ways to copy data from the query results grid. """ - scenarios = [ - ("Test Copying Query Results", dict()) + ("Copy rows, column using button and keyboard shortcut", dict()) ] def before(self): @@ -50,27 +50,82 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe")) self.page.find_by_id("btn-flash").click() + time.sleep(5) self._copies_rows() self._copies_columns() + self._copies_row_using_keyboard_shortcut() + self._copies_column_using_keyboard_shortcut() + self._shift_resizes_rectangular_selection() + self._shift_resizes_column_selection() def _copies_rows(self): pyperclip.copy("old clipboard contents") - time.sleep(5) - self.page.find_by_xpath("//*[contains(@class, 'sr')]/*[1]/input[@type='checkbox']").click() + self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]").click() + self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertEqual("'Some-Name','6'", + self.assertEqual("'Some-Name','6','some info'", pyperclip.paste()) def _copies_columns(self): pyperclip.copy("old clipboard contents") - - self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]/input").click() + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() self.assertEqual( """'Some-Name' -'Some-Other-Name'""", +'Some-Other-Name' +'Yet-Another-Name'""", + pyperclip.paste()) + + def _copies_row_using_keyboard_shortcut(self): + pyperclip.copy("old clipboard contents") + self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]/input[@type='checkbox']").click() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("'Some-Name','6','some info'", + pyperclip.paste()) + + def _copies_column_using_keyboard_shortcut(self): + pyperclip.copy("old clipboard contents") + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual( + """'Some-Name' +'Some-Other-Name' +'Yet-Another-Name'""", + pyperclip.paste()) + + def _shift_resizes_rectangular_selection(self): + pyperclip.copy("old clipboard contents") + + top_left_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") + initial_bottom_right_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") + ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(initial_bottom_right_cell)\ + .release(initial_bottom_right_cell).perform() + + ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_RIGHT).key_up(Keys.SHIFT).perform() + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("""'Some-Other-Name','22','some other info' +'Yet-Another-Name','14','cool info'""", pyperclip.paste()) + + def _shift_resizes_column_selection(self): + pyperclip.copy("old clipboard contents") + + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'value')]").click() + ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_LEFT)\ + .key_up(Keys.SHIFT).perform() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual( + """'Some-Name','6' +'Some-Other-Name','22' +'Yet-Another-Name','14'""", pyperclip.paste()) def after(self): diff --git a/web/pgadmin/static/js/selection/active_cell_capture.js b/web/pgadmin/static/js/selection/active_cell_capture.js new file mode 100644 index 00000000..0abb7355 --- /dev/null +++ b/web/pgadmin/static/js/selection/active_cell_capture.js @@ -0,0 +1,160 @@ +define([ + 'jquery', + 'sources/selection/range_selection_helper' +], function ($, rangeSelectionHelper) { + + var ActiveCellCapture = function () { + var KEY_RIGHT = 39; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_DOWN = 40; + + var bypassDefaultActiveCellRangeChange = false; + var grid; + + var init = function (slickGrid) { + grid = slickGrid; + grid.onDragEnd.subscribe(onDragEndHandler); + grid.onHeaderClick.subscribe(onHeaderClickHandler); + grid.onClick.subscribe(onClickHandler); + grid.onActiveCellChanged.subscribe(onActiveCellChangedHandler); + grid.onKeyDown.subscribe(onKeyDownHandler); + }; + + var destroy = function () { + grid.onDragEnd.unsubscribe(onDragEndHandler); + grid.onHeaderClick.unsubscribe(onHeaderClickHandler); + grid.onActiveCellChanged.unsubscribe(onActiveCellChangedHandler); + grid.onKeyDown.unsubscribe(onKeyDownHandler); + }; + + $.extend(this, { + "init": init, + "destroy": destroy, + }); + + function onDragEndHandler(event, dragData) { + bypassDefaultActiveCellRangeChange = true; + grid.setActiveCell(dragData.range.start.row, dragData.range.start.cell); + } + + function onHeaderClickHandler(event, args) { + bypassDefaultActiveCellRangeChange = true; + + var clickedColumn = args.column.pos + 1; + if (isClickingLastClickedHeader(0, clickedColumn)) { + if (isSingleRangeSelected()) { + grid.resetActiveCell(); + } else { + grid.setActiveCell(0, retrievePreviousSelectedRange().fromCell); + } + } else if (!isClickingInSelectedColumn(clickedColumn)) { + grid.setActiveCell(0, clickedColumn); + } + } + + function isEditableNewRow(row) { + return row >= grid.getDataLength(); + } + + function onClickHandler(event, args) { + if (isRowHeader(args.cell)) { + bypassDefaultActiveCellRangeChange = true; + var rowClicked = args.row; + + if (isEditableNewRow(rowClicked)) { + return; + } + + if (isClickingLastClickedHeader(rowClicked, 1)) { + if (isSingleRangeSelected()) { + grid.resetActiveCell(); + } else { + grid.setActiveCell(retrievePreviousSelectedRange().fromRow, 1); + } + } else if (!isClickingInSelectedRow(rowClicked)) { + grid.setActiveCell(rowClicked, 1); + } + } + } + + function onActiveCellChangedHandler(event, args) { + if (bypassDefaultActiveCellRangeChange) { + bypassDefaultActiveCellRangeChange = false; + event.stopPropagation(); + } + } + + function onKeyDownHandler(event) { + if (hasActiveCell() && isShiftArrowKey(event)) { + selectOnlyRangeOfActiveCell(); + } + } + + function isClickingLastClickedHeader(clickedRow, clickedColumn) { + return hasActiveCell() && grid.getActiveCell().row === clickedRow && grid.getActiveCell().cell === clickedColumn; + } + + function isClickingInSelectedColumn(clickedColumn) { + var column = rangeSelectionHelper.rangeForColumn(grid, clickedColumn); + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return rangeSelectionHelper.isRangeSelected(ranges, column); + } + + function isRowHeader(cellClicked) { + return grid.getColumns()[cellClicked].id === 'row-header-column'; + } + + function isClickingInSelectedRow(rowClicked) { + var row = rangeSelectionHelper.rangeForRow(grid, rowClicked); + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return rangeSelectionHelper.isRangeSelected(ranges, row); + } + + function isSingleRangeSelected() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return ranges.length === 1; + } + + function retrievePreviousSelectedRange() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return ranges[ranges.length - 2]; + } + + function isArrowKey(event) { + return event.which === KEY_RIGHT + || event.which === KEY_UP + || event.which === KEY_LEFT + || event.which === KEY_DOWN; + } + + function isModifiedByShiftOnly(event) { + return event.shiftKey + && !event.ctrlKey + && !event.altKey; + } + + function isShiftArrowKey(event) { + return isModifiedByShiftOnly(event) && isArrowKey(event); + } + + function hasActiveCell() { + return !!grid.getActiveCell(); + } + + function selectOnlyRangeOfActiveCell() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + + if (ranges.length > 1) { + cellSelectionModel.setSelectedRanges([ranges.pop()]); + } + } + }; + + return ActiveCellCapture; +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js index c89b3fa8..2923a796 100644 --- a/web/pgadmin/static/js/selection/column_selector.js +++ b/web/pgadmin/static/js/selection/column_selector.js @@ -1,20 +1,29 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) { var ColumnSelector = function () { + var Slick = window.Slick; + var gridEventBus = new Slick.EventHandler(); + var init = function (grid) { - grid.onHeaderClick.subscribe(function (event, eventArgument) { - var column = eventArgument.column; + gridEventBus.subscribe(grid.onHeaderClick, function (event, eventArgument) { + var columnDefinition = eventArgument.column; - if (column.selectable !== false) { + grid.focus(); - if (!clickedCheckbox(event)) { - var $checkbox = $("[data-id='checkbox-" + column.id + "']"); - toggleCheckbox($checkbox); - } + if (isColumnSelectable(columnDefinition)) { + var $columnHeader = $(event.target); + if(hasClickedChildOfColumnHeader(event)) { + $columnHeader = $(event.target).parents(".slick-header-column"); + } + $columnHeader.toggleClass('selected'); - updateRanges(grid, column.id); + if (!hasClickedOnCheckbox(event)) { + var $checkbox = $("[data-id='checkbox-" + columnDefinition.id + "']"); + toggleCheckbox($checkbox); } + + updateRanges(grid, columnDefinition.id); } - ); + }); grid.getSelectionModel().onSelectedRangesChanged .subscribe(handleSelectedRangesChanged.bind(null, grid)); }; @@ -24,7 +33,8 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func .each(function (index, checkbox) { var $checkbox = $(checkbox); var columnIndex = grid.getColumnIndex($checkbox.data('column-id')); - var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, rangeSelectionHelper.rangeForColumn(grid, columnIndex)); + var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, + rangeSelectionHelper.rangeForColumn(grid, columnIndex)); if (!isStillSelected) { toggleCheckbox($checkbox); } @@ -51,8 +61,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func selectionModel.setSelectedRanges(newRanges); }; - var clickedCheckbox = function (e) { - return e.target.type == "checkbox" + var hasClickedOnCheckbox = function (event) { + return event.target.type == "checkbox" + }; + + + var hasClickedChildOfColumnHeader = function (event) { + return !$(event.target).hasClass("slick-header-column"); }; var toggleCheckbox = function (checkbox) { @@ -63,9 +78,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func } }; + var isColumnSelectable = function (columnDefinition) { + return columnDefinition.selectable !== false; + }; + var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) { return _.map(columnDefinitions, function (columnDefinition) { - if (columnDefinition.selectable !== false) { + if (isColumnSelectable(columnDefinition)) { var name = "" + diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js index 018efead..166219ba 100644 --- a/web/pgadmin/static/js/selection/copy_data.js +++ b/web/pgadmin/static/js/selection/copy_data.js @@ -3,8 +3,9 @@ define([ 'underscore', 'sources/selection/clipboard', 'sources/selection/range_selection_helper', - 'sources/selection/range_boundary_navigator'], - function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { + 'sources/selection/range_boundary_navigator' + ], +function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { var copyData = function () { var self = this; @@ -14,7 +15,6 @@ define([ var data = grid.getData(); var rows = grid.getSelectedRows(); - if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) { self.copied_rows = rows.map(function (rowIndex) { return data[rowIndex]; @@ -24,7 +24,6 @@ define([ self.copied_rows = []; setPasteRowButtonEnablement(self.can_edit, false); } - var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges); if (csvText) { clipboard.copyTextToClipboard(csvText); @@ -45,8 +44,8 @@ define([ if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) { return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); } - return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]); + return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); }; - return copyData; -}); + return copyData +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js index 31aee69f..90eeacf8 100644 --- a/web/pgadmin/static/js/selection/grid_selector.js +++ b/web/pgadmin/static/js/selection/grid_selector.js @@ -1,5 +1,8 @@ -define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'], - function ($, ColumnSelector, RowSelector) { +define(['jquery', + 'sources/selection/column_selector', + 'sources/selection/row_selector', + 'sources/selection/range_selection_helper'], + function ($, ColumnSelector, RowSelector, RangeSelectionHelper) { var Slick = window.Slick; var GridSelector = function (columnDefinitions) { @@ -31,44 +34,22 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se }; function handleSelectedRangesChanged(grid) { - $("[data-id='checkbox-select-all']").prop("checked", isEntireGridSelected(grid)); - } - - function isEntireGridSelected(grid) { - var selectionModel = grid.getSelectionModel(); - var selectedRanges = selectionModel.getSelectedRanges(); - return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid)); + $("[data-id='checkbox-select-all']").prop("checked", RangeSelectionHelper.isEntireGridSelected(grid)); } function toggleSelectAll(grid) { - if (isEntireGridSelected(grid)) { - deselect(grid); + if (RangeSelectionHelper.isEntireGridSelected(grid)) { + selectNone(grid); } else { - selectAll(grid) + RangeSelectionHelper.selectAll(grid); } } - var isSameRange = function (range, otherRange) { - return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell && - range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow; - }; - - function getRangeOfWholeGrid(grid) { - return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1); - } - - function deselect(grid) { + function selectNone(grid) { var selectionModel = grid.getSelectionModel(); selectionModel.setSelectedRanges([]); } - function selectAll(grid) { - var range = getRangeOfWholeGrid(grid); - var selectionModel = grid.getSelectionModel(); - - selectionModel.setSelectedRanges([range]); - } - $.extend(this, { "init": init, "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js index a268d245..f88a6533 100644 --- a/web/pgadmin/static/js/selection/range_boundary_navigator.js +++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js @@ -1,4 +1,5 @@ -define(['sources/selection/range_selection_helper'], function (RangeSelectionHelper) { +define(['sources/selection/range_selection_helper'], +function (RangeSelectionHelper) { return { getUnion: function (allRanges) { if (_.isEmpty(allRanges)) { @@ -77,6 +78,10 @@ define(['sources/selection/range_selection_helper'], function (RangeSelectionHel removeFirstColumn: function (colRangeBounds) { var unionedColRanges = this.getUnion(colRangeBounds); + if(unionedColRanges.length == 0) { + return []; + } + var firstSubrangeStartsAt0 = function () { return unionedColRanges[0][0] == 0; }; diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js index 31ad3bf7..6c69c390 100644 --- a/web/pgadmin/static/js/selection/range_selection_helper.js +++ b/web/pgadmin/static/js/selection/range_selection_helper.js @@ -45,7 +45,7 @@ define(['slickgrid'], function () { return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1); }; - function rangeForColumn(grid, columnIndex) { + var rangeForColumn = function (grid, columnIndex) { return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex) }; @@ -63,6 +63,13 @@ define(['slickgrid'], function () { return !_.isUndefined(columnDefinitions[0].pos); }; + function selectAll(grid) { + var range = getRangeOfWholeGrid(grid); + var selectionModel = grid.getSelectionModel(); + + selectionModel.setSelectedRanges([range]); + } + return { addRange: addRange, removeRange: removeRange, @@ -73,6 +80,7 @@ define(['slickgrid'], function () { rangeForColumn: rangeForColumn, isEntireGridSelected: isEntireGridSelected, getRangeOfWholeGrid: getRangeOfWholeGrid, - isFirstColumnData: isFirstColumnData + isFirstColumnData: isFirstColumnData, + selectAll: selectAll, } }); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js index 76a8c1a7..8124b5cb 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -19,7 +19,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func } updateRanges(grid, args.row); } - } + }; var handleSelectedRangesChanged = function (grid, event, ranges) { $('[data-cell-type="row-header-checkbox"]:checked') @@ -32,7 +32,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func toggleCheckbox($checkbox); } }); - } + }; var updateRanges = function (grid, rowId) { var selectionModel = grid.getSelectionModel(); @@ -81,5 +81,6 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes }); }; + return RowSelector; }); diff --git a/web/pgadmin/static/js/selection/set_staged_rows.js b/web/pgadmin/static/js/selection/set_staged_rows.js index cace7282..46aa8c8c 100644 --- a/web/pgadmin/static/js/selection/set_staged_rows.js +++ b/web/pgadmin/static/js/selection/set_staged_rows.js @@ -91,6 +91,11 @@ define( } var selectedRows = this.selection.getSelectedRows(); + var selectedRanges = this.selection.getSelectedRanges(); + + if (selectedRanges.length > 0) { + enableButton('#btn-copy-row'); + } if (selectedRows.length > 0) { var stagedRows = getPrimaryKeysForSelectedRows(self, selectedRows); @@ -99,7 +104,6 @@ define( this.selection.setSelectedRows([]); } - enableButton('#btn-copy-row'); if (isEditMode()) { enableButton('#btn-delete-row'); } diff --git a/web/pgadmin/static/js/slickgrid/cell_selector.js b/web/pgadmin/static/js/slickgrid/cell_selector.js new file mode 100644 index 00000000..a709351e --- /dev/null +++ b/web/pgadmin/static/js/slickgrid/cell_selector.js @@ -0,0 +1,18 @@ +define(["slickgrid"], function () { + var Slick = window.Slick; + + return function () { + this.init = function (grid) { + grid.onActiveCellChanged.subscribe(function (event, slickEvent) { + grid.getSelectionModel().setSelectedRanges([ + new Slick.Range( + slickEvent.row, + slickEvent.cell, + slickEvent.row, + slickEvent.cell + ) + ]); + }); + } + } +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js new file mode 100644 index 00000000..2a319407 --- /dev/null +++ b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js @@ -0,0 +1,21 @@ +define([ + 'sources/selection/copy_data', + 'sources/selection/range_selection_helper' + ], +function (copyData, RangeSelectionHelper) { + return function handleQueryOutputKeyboardEvent(event, args) { + var KEY_C = 67; + var KEY_A = 65; + var modifiedKey = event.keyCode; + var isModifierDown = event.ctrlKey || event.metaKey; + this.slickgrid = args.grid; + + if (isModifierDown && modifiedKey == KEY_C) { + copyData.apply(this); + } + + if (isModifierDown && modifiedKey == KEY_A) { + RangeSelectionHelper.selectAll(this.slickgrid); + } + } +}); \ No newline at end of file diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 76654d33..d2d9d079 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -279,7 +279,7 @@ li { background-color: white; } -.sc.cell-move-handle { +.slick-cell.cell-move-handle { font-weight: bold; text-align: right; border-right: solid gray; @@ -291,15 +291,15 @@ li { background: #b6b9bd; } -.sr.selected .cell-move-handle { +.slick-row.selected .cell-move-handle { background: #D5DC8D; } -.sr .cell-actions { +.slick-row .cell-actions { text-align: left; } -.sr.complete { +.slick-row.complete { background-color: #DFD; color: #555; } @@ -332,7 +332,7 @@ li { font-size: 10px; } -.sr.selected .cell-selection { +.slick-row.selected .cell-selection { background-color: transparent; /* show default selected row background */ } @@ -359,6 +359,10 @@ li { display: table-cell; } +.column-description { + display: table-cell; +} + .long_text_editor { margin-left: 5px; font-size: 12px !important; @@ -401,10 +405,17 @@ input.editor-checkbox:focus { } /* Override selected row color */ -.sc.selected { +.slick-cell.selected { background-color: #DEE8F1 !important; } +/* Remove active cell border */ +.slick-cell.active { + border: 1px solid transparent; + border-right: 1px dotted silver; + border-bottom-color: silver; +} + /* To highlight all newly inserted rows */ .grid-canvas .new_row { background: #dff0d7; @@ -431,8 +442,8 @@ input.editor-checkbox:focus { } /* color the first column */ -.sr .sc:first-child { - background-color: #e8e8e8; +.slick-row .slick-cell.l0.r0 { + background-color: #e8e8e8; } #datagrid div.slick-header.ui-state-default { @@ -453,6 +464,11 @@ input.editor-checkbox:focus { -ms-box-sizing: content-box; } -.sr.ui-widget-content { +.slick-row.ui-widget-content { border-top: 1px solid silver; +} + +.select-all-icon { + margin-left: 12px; + vertical-align: bottom; } \ No newline at end of file diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js index 8d835e35..c064a9bb 100644 --- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js @@ -1,33 +1,42 @@ -define([ - 'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'alertify', - 'pgadmin', 'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain', - 'sources/selection/grid_selector', 'sources/selection/clipboard', - 'sources/selection/copy_data', - 'sources/selection/set_staged_rows', - 'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker', - 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', - 'codemirror/addon/selection/active-line', 'codemirror/addon/fold/foldcode', - 'codemirror/addon/fold/foldgutter', 'codemirror/addon/hint/show-hint', - 'codemirror/addon/hint/sql-hint', 'pgadmin.file_manager', - 'pgadmin-sqlfoldcode', - 'codemirror/addon/scroll/simplescrollbars', - 'codemirror/addon/dialog/dialog', - 'codemirror/addon/search/search', - 'codemirror/addon/search/searchcursor', - 'codemirror/addon/search/jump-to-line', - 'backgrid.sizeable.columns', 'slickgrid/slick.formatters', - 'slick.pgadmin.formatters', 'slickgrid/slick.editors', - 'slick.pgadmin.editors', 'slickgrid/plugins/slick.autotooltips', - 'slickgrid/plugins/slick.cellrangedecorator', - 'slickgrid/plugins/slick.cellrangeselector', - 'slickgrid/plugins/slick.cellselectionmodel', - 'slickgrid/plugins/slick.cellcopymanager', - 'slickgrid/plugins/slick.rowselectionmodel', - 'slickgrid/slick.grid' -], function( - gettext, $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, - pgExplain, GridSelector, clipboard, copyData, setStagedRows -) { +define( + [ + 'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', + 'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain', + 'sources/selection/grid_selector', + 'sources/selection/active_cell_capture', + 'sources/selection/clipboard', + 'sources/selection/copy_data', + 'sources/selection/range_selection_helper', + 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event', + 'sources/selection/set_staged_rows', + 'sources/gettext', + + 'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker', + 'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection', + 'codemirror/addon/selection/active-line', 'codemirror/addon/fold/foldcode', + 'codemirror/addon/fold/foldgutter', 'codemirror/addon/hint/show-hint', + 'codemirror/addon/hint/sql-hint', 'pgadmin.file_manager', + 'pgadmin-sqlfoldcode', + 'codemirror/addon/scroll/simplescrollbars', + 'codemirror/addon/dialog/dialog', + 'codemirror/addon/search/search', + 'codemirror/addon/search/searchcursor', + 'codemirror/addon/search/jump-to-line', + 'backgrid.sizeable.columns', 'slickgrid/slick.formatters', + 'slick.pgadmin.formatters', 'slickgrid/slick.editors', + 'slick.pgadmin.editors', 'slickgrid/plugins/slick.autotooltips', + 'slickgrid/plugins/slick.cellrangedecorator', + 'slickgrid/plugins/slick.cellrangeselector', + 'slickgrid/plugins/slick.cellselectionmodel', + 'slickgrid/plugins/slick.cellcopymanager', + 'slickgrid/plugins/slick.rowselectionmodel', + 'slickgrid/slick.grid' + ], + function( + $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, + ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, + setStagedRows, gettext + ) { /* Return back, this has been called more than once */ if (pgAdmin.SqlEditor) return pgAdmin.SqlEditor; @@ -676,7 +685,8 @@ define([ var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options); grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) ); - grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); + grid.registerPlugin(new ActiveCellCapture()); + grid.setSelectionModel(new Slick.CellSelectionModel()); grid.registerPlugin(gridSelector); var editor_data = { @@ -730,41 +740,7 @@ define([ } }); - // Listener function for COPY/PASTE operation on grid - grid.onKeyDown.subscribe(function (e, args) { - var c = e.keyCode, - ctrlDown = e.ctrlKey||e.metaKey; // Mac support - - // (ctrlDown && c==67) return false // c - // (ctrlDown && c==86) return false // v - // (ctrlDown && c==88) return false // x - - - if (!ctrlDown && !(c==67 || c==86 || c==88)) { - return; // Not a copy paste opration - } - - var grid = args.grid, column_info, column_values, value, - cell = args.cell, row = args.row; - - // Copy operation (Only when if there is no row selected) - // When user press `Ctrl + c` on selected cell - if(ctrlDown && c==67) { - // May be single cell is selected - column_info = grid.getColumns()[cell] - // Fetch current row data from grid - column_values = grid.getDataItem(row, cell) - // Get the value from cell - value = column_values[column_info.pos] || ''; - // Copy this value to Clipboard - if(value) - clipboard.copyTextToClipboard(value); - // Go to cell again - grid.gotoCell(row, cell, false); - } - - }); - + grid.onKeyDown.subscribe(handleQueryOutputKeyboardEvent); // Listener function which will be called when user updates existing rows grid.onCellChange.subscribe(function (e, args) { diff --git a/web/pgadmin/utils/tests/test_versioned_template_loader.py b/web/pgadmin/utils/tests/test_versioned_template_loader.py index 4ba56033..a0d97899 100644 --- a/web/pgadmin/utils/tests/test_versioned_template_loader.py +++ b/web/pgadmin/utils/tests/test_versioned_template_loader.py @@ -19,16 +19,27 @@ from pgadmin.utils.route import BaseTestGenerator class TestVersionedTemplateLoader(BaseTestGenerator): scenarios = [ - ("Test versioned template loader", dict()) + ("Render a template when called", dict(scenario=1)), + ("Render a version 9.1 template when it is present", dict(scenario=2)), + ("Render a version 9.2 template when request for a higher version", dict(scenario=3)), + ("Render default version when version 9.0 was requested and only 9.1 and 9.2 are present", dict(scenario=4)), + ("Raise error when version is smaller than available templates", dict(scenario=5)) ] def setUp(self): self.loader = VersionedTemplateLoader(FakeApp()) def runTest(self): - self.test_get_source_returns_a_template() - self.test_get_source_when_the_version_is_9_1_returns_9_1_template() - self.test_get_source_when_the_version_is_9_3_and_there_are_templates_for_9_2_and_9_1_returns_9_2_template() + if self.scenario == 1: + self.test_get_source_returns_a_template() + if self.scenario == 2: + self.test_get_source_when_the_version_is_9_1_returns_9_1_template() + if self.scenario == 3: + self.test_get_source_when_the_version_is_9_3_and_there_are_templates_for_9_2_and_9_1_returns_9_2_template() + if self.scenario == 4: + self.test_get_source_when_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template() + if self.scenario == 5: + self.test_raise_not_found_exception_when_postgres_version_less_than_all_available_sql_templates() def test_get_source_returns_a_template(self): expected_content = "Some SQL" \ @@ -57,10 +68,12 @@ class TestVersionedTemplateLoader(BaseTestGenerator): self.assertEqual("Some 9.2 SQL", str(content).replace("\r","")) self.assertIn(sql_path, filename) - def test_get_source_when_the_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template(self): + def test_get_source_when_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template(self): # For cross platform we join the SQL path (This solves the slashes issue) sql_path = os.path.join("some_feature", "sql", "default", "some_action_with_default.sql") - content, filename, up_to_dateness = self.loader.get_source(None, "some_feature/sql/#90000#/some_action_with_default.sql") + content, filename, up_to_dateness = self.loader.get_source( + None, + "some_feature/sql/#90000#/some_action_with_default.sql") self.assertEqual("Some default SQL", str(content).replace("\r","")) self.assertIn(sql_path, filename) diff --git a/web/regression/javascript/selection/active_cell_capture_spec.js b/web/regression/javascript/selection/active_cell_capture_spec.js new file mode 100644 index 00000000..46c24776 --- /dev/null +++ b/web/regression/javascript/selection/active_cell_capture_spec.js @@ -0,0 +1,239 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +define([ + 'sources/selection/active_cell_capture', + 'sources/selection/range_selection_helper', +], function (ActiveCellCapture, RangeSelectionHelper) { + describe('ActiveCellCapture', function () { + var grid, activeCellPlugin, getSelectedRangesSpy, setSelectedRangesSpy; + var onHeaderClickFunction, onClickFunction; + beforeEach(function () { + getSelectedRangesSpy = jasmine.createSpy('getSelectedRangesSpy'); + setSelectedRangesSpy = jasmine.createSpy('setSelectedRangesSpy'); + grid = { + getSelectionModel: function () { + return { + getSelectedRanges: getSelectedRangesSpy, + setSelectedRanges: setSelectedRangesSpy, + } + }, + + getColumns: function () { + return [ + {id: 'row-header-column'}, + {id: 'column-1'}, + {id: 'column-2'} + ] + }, + + onDragEnd: jasmine.createSpyObj('onDragEnd', ['subscribe']), + onActiveCellChanged: jasmine.createSpyObj('onActiveCellChanged', ['subscribe']), + onHeaderClick: jasmine.createSpy('onHeaderClick'), + onClick: jasmine.createSpy('onClick'), + onKeyDown: jasmine.createSpyObj('onKeyDown', ['subscribe']), + + getDataLength: function () { return 10 }, + + setActiveCell: jasmine.createSpy('setActiveCell'), + getActiveCell: jasmine.createSpy('getActiveCell'), + resetActiveCell: jasmine.createSpy('resetActiveCell'), + }; + activeCellPlugin = new ActiveCellCapture(); + + grid.onHeaderClick.subscribe = jasmine.createSpy('subscribe').and.callFake(function (callback) { + onHeaderClickFunction = callback.bind(activeCellPlugin); + }); + + grid.onClick.subscribe = jasmine.createSpy('subscribe').and.callFake(function (callback) { + onClickFunction = callback.bind(activeCellPlugin); + }); + + activeCellPlugin.init(grid); + }); + + describe('onHeaderClickHandler', function () { + describe('when no ranges are selected', function () { + beforeEach(function () { + getSelectedRangesSpy.and.returnValue([]); + onHeaderClickFunction({}, {column: {pos: 1}}); + grid.getActiveCell.and.returnValue(null); + }); + + it('should set the active cell', function () { + expect(grid.setActiveCell).toHaveBeenCalledWith(0, 2); + }); + + it('should not change the selected ranges', function () { + expect(setSelectedRangesSpy).not.toHaveBeenCalled(); + }); + }); + + describe('when only one column is already selected', function () { + beforeEach(function () { + getSelectedRangesSpy.and.returnValue([RangeSelectionHelper.rangeForColumn(grid, 2)]); + grid.getActiveCell.and.returnValue({cell: 2, row: 0}); + }); + + describe('when a different column is clicked', function () { + beforeEach(function () { + onHeaderClickFunction({}, {column: {pos: 4}}) + }); + + it('should set the active cell to the newly clicked columns top cell', function () { + expect(grid.setActiveCell).toHaveBeenCalledWith(0, 5); + }); + }); + + describe('when the same column is clicked', function () { + beforeEach(function () { + onHeaderClickFunction({}, {column: {pos: 1}}) + }); + + it('should reset the active cell', function () { + expect(grid.resetActiveCell).toHaveBeenCalled(); + }); + }); + }); + + describe('when three non-consecutive columns are selected', function () { + beforeEach(function () { + getSelectedRangesSpy.and.returnValue([ + RangeSelectionHelper.rangeForColumn(grid, 10), + RangeSelectionHelper.rangeForColumn(grid, 6), + RangeSelectionHelper.rangeForColumn(grid, 22), + ]); + grid.getActiveCell.and.returnValue({cell: 22, row: 0}); + }); + + describe('when the third column is clicked (thereby deselecting it)', function () { + beforeEach(function () { + onHeaderClickFunction({}, {column: {pos: 21}}) + }); + + it('should set the active cell to the second column', function () { + expect(grid.setActiveCell).toHaveBeenCalledWith(0, 6); + }); + }); + + describe('when the second column is clicked (thereby deselecting it)', function () { + beforeEach(function () { + onHeaderClickFunction({}, {column: {pos: 5}}) + }); + + it('should not set the active cell', function () { + expect(grid.setActiveCell).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('onClick', function () { + describe('when the target is a random cell in the grid', function () { + it('should not change the active cell', function () { + onClickFunction({}, {row: 1, cell: 2}); + grid.getActiveCell.and.returnValue(null); + + expect(grid.setActiveCell).not.toHaveBeenCalled(); + }); + }); + + describe('when the target is the row header', function () { + describe('when no rows are selected', function () { + beforeEach(function () { + getSelectedRangesSpy.and.returnValue([]); + onClickFunction({}, {row: 1, cell: 0}); + grid.getActiveCell.and.returnValue(null); + }); + + it('changes the active cell', function () { + expect(grid.setActiveCell).toHaveBeenCalledWith(1, 1); + }); + + it('should not change the selected ranges', function () { + expect(setSelectedRangesSpy).not.toHaveBeenCalled(); + }); + }); + + describe('when there is one cell selected', function () { + beforeEach(function () { + grid.getActiveCell.and.returnValue({row: 4, cell: 5}); + getSelectedRangesSpy.and.returnValue([ + new Slick.Range(4, 5) + ]); + }); + + it('sets the active cell', function () { + onClickFunction({}, {row: 4, cell: 0}); + + expect(grid.setActiveCell).toHaveBeenCalledWith(4, 1); + }); + }); + + describe('when there is one row selected', function () { + beforeEach(function () { + grid.getActiveCell.and.returnValue({row: 3, cell: 1}); + getSelectedRangesSpy.and.returnValue([ + RangeSelectionHelper.rangeForRow(grid, 3) + ]); + }); + + it('resets the selected row', function () { + onClickFunction({}, {row: 3, cell: 0}); + + expect(grid.resetActiveCell).toHaveBeenCalled(); + }); + + describe('when we select a different row', function () { + it('should change the active cell', function () { + onClickFunction({}, {row: 9, cell: 0}); + + expect(grid.setActiveCell).toHaveBeenCalledWith(9, 1); + }); + }); + }); + + describe('when there are 2 rows selected', function () { + beforeEach(function () { + grid.getActiveCell.and.returnValue({row: 3, cell: 1}); + getSelectedRangesSpy.and.returnValue([ + RangeSelectionHelper.rangeForRow(grid, 5), + RangeSelectionHelper.rangeForRow(grid, 3) + ]); + }); + + describe('when the last selected row is clicked again', function () { + it('should change the active cell to the first selected row', function () { + onClickFunction({}, {row: 3, cell: 0}); + + expect(grid.setActiveCell).toHaveBeenCalledWith(5, 1); + }); + }); + + describe('when the first selected row is clicked again', function () { + it('should not change the active cell', function () { + onClickFunction({}, {row: 5, cell: 0}); + + expect(grid.setActiveCell).not.toHaveBeenCalled(); + }); + }); + }); + + describe('and the editable new row', function () { + beforeEach(function () { + onClickFunction({}, {row: 10, cell: 0}) + }); + it('does not select the row', function () { + expect(grid.setActiveCell).not.toHaveBeenCalled(); + }); + }); + }); + }); + }); +}); diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js index 947f4852..4526b602 100644 --- a/web/regression/javascript/selection/column_selector_spec.js +++ b/web/regression/javascript/selection/column_selector_spec.js @@ -1,32 +1,63 @@ define( ["jquery", "underscore", - "slickgrid/slick.grid", "sources/selection/column_selector", - "slickgrid/slick.rowselectionmodel", - "slickgrid" + "sources/selection/active_cell_capture", + "sources/selection/grid_selector", + + "slickgrid", + 'slickgrid/slick.cellrangedecorator', + 'slickgrid/slick.cellrangeselector', + "slickgrid/slick.cellselectionmodel", + "slickgrid/slick.grid", ], - function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) { + function ($, _, ColumnSelector, ActiveCellCapture, GridSelector) { + var KEY_RIGHT = 39; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_DOWN = 40; + + var Slick = window.Slick; + var SlickGrid = Slick.Grid; + describe("ColumnSelector", function () { var container, data, columns, options; beforeEach(function () { container = $("
"); container.height(9999); + container.width(9999); - data = [{'some-column-name': 'first value', 'second column': 'second value'}]; + data = [{ + 'some-column-name': 'first value', + 'second column': 'second value', + 'third column': 'nonselectable value' + }]; columns = [ { + id: 'row-header-column', + name: 'row header column name', + selectable: false, + }, + { id: '1', name: 'some-column-name', + pos: 0 }, { id: '2', name: 'second column', + pos: 1 + }, + { + id: 'third-column-id', + name: 'third column', + pos: 2 }, { name: 'some-non-selectable-column', - selectable: false + selectable: false, + pos: 3 } ] }); @@ -41,22 +72,22 @@ define( setupGrid(columns); - expect(container.find('.slick-header-columns input').length).toBe(2) + expect(container.find('.slick-header-columns input').length).toBe(3) }); }); it("renders a checkbox in the column header", function () { setupGrid(columns); - expect(container.find('.slick-header-columns input').length).toBe(2) + expect(container.find('.slick-header-columns input').length).toBe(3) }); it("displays the name of the column", function () { setupGrid(columns); - expect($(container.find('.slick-header-columns .slick-column-name')[0]).text()) - .toContain('some-column-name'); expect($(container.find('.slick-header-columns .slick-column-name')[1]).text()) + .toContain('some-column-name'); + expect($(container.find('.slick-header-columns .slick-column-name')[2]).text()) .toContain('second column'); }); @@ -64,22 +95,28 @@ define( var columnSelector = new ColumnSelector(); var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); - expect(selectableColumns[0].id).toBe('1'); + expect(selectableColumns[1].id).toBe('1'); }); - describe("selecting columns", function () { - var grid, rowSelectionModel; + describe("with ActiveCellCapture, CellSelectionModel, and GridSelector: selecting columns", function () { + var grid, cellSelectionModel; beforeEach(function () { var columnSelector = new ColumnSelector(); columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); data = []; for (var i = 0; i < 10; i++) { - data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); + data.push({ + 'some-column-name': 'some-value-' + i, + 'second column': 'second value ' + i, + 'third column': 'third value ' + i, + 'fourth column': 'fourth value ' + i, + }); } - grid = new SlickGrid(container, data, columns, options); + grid = new SlickGrid(container, data, columns); - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); + grid.registerPlugin(new ActiveCellCapture()); + cellSelectionModel = new Slick.CellSelectionModel(); + grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(columnSelector); grid.invalidate(); @@ -92,144 +129,214 @@ define( describe("when the user clicks a column header", function () { it("selects the column", function () { - container.find('.slick-header-column')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + container.find('.slick-header-column:contains(some-column-name)').click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstColumnToBeSelected(selectedRanges); }); + + xit("toggles a selected class to the header cell", function () { + container.find('.slick-header-column:contains(second column)').click(); + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBe(true); + + container.find('.slick-header-column:contains(second column)').click(); + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBe(false); + }); }); - describe("when the user clicks additional column headers", function () { + describe("when the user clicks an additional column header", function () { beforeEach(function () { - container.find('.slick-header-column')[1].click(); + container.find('.slick-header-column:contains(some-column-name)').click(); }); it("selects additional columns", function () { - container.find('.slick-header-column')[0].click(); + container.find('.slick-header-column:contains(second column)').click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - var column1 = selectedRanges[0]; + var selectedRanges = cellSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toEqual(2); + expect(selectedRanges.length).toBe(2); + var column1 = selectedRanges[0]; expect(column1.fromCell).toBe(1); expect(column1.toCell).toBe(1); var column2 = selectedRanges[1]; + expect(column2.fromCell).toBe(2); + expect(column2.toCell).toBe(2); + }); + + describe("and presses shift + right-arrow", function () { + beforeEach(function () { + container.find('.slick-header-column:contains(second column)').click(); + }); - expect(column2.fromCell).toBe(0); - expect(column2.toCell).toBe(0); + it("keeps the last column selected", function () { + pressShiftArrow(KEY_RIGHT); + + expect(cellSelectionModel.getSelectedRanges().length).toBe(1); + }); + + it("grows the selection to the right", function () { + pressShiftArrow(KEY_RIGHT); + + var selectedRange = cellSelectionModel.getSelectedRanges()[0]; + expect(selectedRange.fromCell).toBe(2); + expect(selectedRange.toCell).toBe(3); + expect(selectedRange.fromRow).toBe(0); + expect(selectedRange.toRow).toBe(9); + }); + }); + + describe("when the user deselects the last selected column header", function () { + beforeEach(function () { + container.find('.slick-header-column:contains(second column)').click(); + container.find('.slick-header-column:contains(second column)').click(); + }); + + describe("and presses shift + right-arrow", function () { + it("first and second columns are selected", function () { + pressShiftArrow(KEY_RIGHT); + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + expect(selectedRanges[0].fromCell).toBe(1); + expect(selectedRanges[0].toCell).toBe(2); + expect(selectedRanges[0].fromRow).toBe(0); + expect(selectedRanges[0].toRow).toBe(9); + }); + }); }); }); - describe("when the user clicks a column header checkbox", function () { + describe("when the user clicks a column header description", function () { it("selects the column", function () { - container.find('.slick-header-columns input')[0].click(); + container.find('.slick-header-columns span.column-description:contains(some-column-name)').click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstColumnToBeSelected(selectedRanges); }); - it("checks the checkbox", function () { - container.find('.slick-header-column')[1].click(); - expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeTruthy(); + xit("toggles a selected class to the header cell", function () { + container.find('.slick-header-column span.column-description:contains(second column)').click(); + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBe(true); + + container.find('.slick-header-column span.column-description:contains(second column)').click(); + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBe(false); + }); - }); describe("when a row is selected", function () { beforeEach(function () { var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; - rowSelectionModel.setSelectedRanges(selectedRanges); + cellSelectionModel.setSelectedRanges(selectedRanges); }); - it("deselects the row", function () { - container.find('.slick-header-column')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + it("deselects the row", function () { + container.find('.slick-header-column')[1].click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); - expect(selectedRanges.length).toBe(1); + expect(selectedRanges.length).toBe(1); - var column = selectedRanges[0]; - - expect(column.fromCell).toBe(1); - expect(column.toCell).toBe(1); - expect(column.fromRow).toBe(0); - expect(column.toRow).toBe(9); - }) - }); + var column = selectedRanges[0]; - describe("clicking a second time", function () { - beforeEach(function () { - container.find('.slick-header-column')[1].click(); + expect(column.fromCell).toBe(1); + expect(column.toCell).toBe(1); + expect(column.fromRow).toBe(0); + expect(column.toRow).toBe(9); + }) }); - it("unchecks checkbox", function () { - container.find('.slick-header-column')[1].click(); - expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); - }); + describe("clicking a second time", function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); + }); - it("deselects the column", function () { - container.find('.slick-header-column')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + it("unchecks checkbox", function () { + container.find('.slick-header-column')[1].click(); + expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); + }); - expect(selectedRanges.length).toEqual(0); - }) - }); + it("deselects the column", function () { + container.find('.slick-header-column')[1].click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); - describe("when the column is not selectable", function () { - it("does not select the column", function () { - $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - - expect(selectedRanges.length).toEqual(0); + expect(selectedRanges.length).toEqual(0); + }) }); - }); - describe("when the column is deselected through setSelectedRanges", function () { - beforeEach(function () { - container.find('.slick-header-column')[1].click(); + describe("when the column is not selectable", function () { + it("does not select the column", function () { + $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toEqual(0); + }); }); - it("unchecks the checkbox", function () { - rowSelectionModel.setSelectedRanges([]); + describe("when the column is deselected through setSelectedRanges", function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); + }); - expect($(container.find('.slick-header-columns input')[1]) - .is(':checked')).toBeFalsy(); - }); - }); + xit("removes selected class from header", function () { + cellSelectionModel.setSelectedRanges([]); - describe("when a non-column range was already selected", function () { - beforeEach(function () { - var selectedRanges = [new Slick.Range(0, 0, 1, 0)]; - rowSelectionModel.setSelectedRanges(selectedRanges); + expect($(container.find('.slick-header-column')[1]).hasClass('selected')) + .toBe(false); + + }); }); - it("deselects the non-column range", function () { - container.find('.slick-header-column')[0].click(); + describe("when a non-column range was already selected", function () { + beforeEach(function () { + var selectedRanges = [new Slick.Range(0, 0, 1, 0)]; + cellSelectionModel.setSelectedRanges(selectedRanges); + }); - var selectedRanges = rowSelectionModel.getSelectedRanges(); - expectOnlyTheFirstColumnToBeSelected(selectedRanges); - }) + it("deselects the non-column range", function () { + container.find('.slick-header-column:contains(some-column-name)').click(); + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + expectOnlyTheFirstColumnToBeSelected(selectedRanges); + }) + }); }); - }); - var setupGrid = function (columns) { + }); + function setupGrid(columns) { var columnSelector = new ColumnSelector(); columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); var grid = new SlickGrid(container, data, columns, options); - var rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); + var cellSelectionModel = new Slick.CellSelectionModel(); + grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(columnSelector); grid.invalidate(); - }; + } function expectOnlyTheFirstColumnToBeSelected(selectedRanges) { var row = selectedRanges[0]; expect(selectedRanges.length).toEqual(1); - expect(row.fromCell).toBe(0); - expect(row.toCell).toBe(0); + expect(row.fromCell).toBe(1); + expect(row.toCell).toBe(1); expect(row.fromRow).toBe(0); expect(row.toRow).toBe(9); } + + function pressShiftArrow(keyCode) { + var pressEvent = new $.Event("keydown"); + pressEvent.shiftKey = true; + pressEvent.ctrlKey = false; + pressEvent.altKey = false; + pressEvent.which = keyCode; + + $(container.find('.grid-canvas')).trigger(pressEvent); + } + }); }); diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js index affad66e..718a830b 100644 --- a/web/regression/javascript/selection/copy_data_spec.js +++ b/web/regression/javascript/selection/copy_data_spec.js @@ -1,3 +1,12 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + define( ["jquery", "slickgrid/slick.grid", diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js index a74a66f9..1c2f7898 100644 --- a/web/regression/javascript/selection/grid_selector_spec.js +++ b/web/regression/javascript/selection/grid_selector_spec.js @@ -13,10 +13,10 @@ define(["jquery", container.height(9999); columns = [{ id: '1', - name: 'some-column-name', + name: 'some-column-name' }, { id: '2', - name: 'second column', + name: 'second column' }]; gridSelector = new GridSelector(); diff --git a/web/regression/javascript/selection/range_boundary_navigator_spec.js b/web/regression/javascript/selection/range_boundary_navigator_spec.js index 8376d0a7..b1ca45eb 100644 --- a/web/regression/javascript/selection/range_boundary_navigator_spec.js +++ b/web/regression/javascript/selection/range_boundary_navigator_spec.js @@ -61,7 +61,6 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); }); - describe("#mapDimensionBoundaryUnion", function () { it("returns a list of the results of the callback", function () { var rangeBounds = [[0, 1], [3, 3]]; @@ -132,20 +131,27 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa }); it("returns csv for the provided ranges", function () { - var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges); expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'"); }); + describe("when no cells are selected", function () { + it("should return an empty string", function () { + var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, []); + + expect(csvResult).toEqual(''); + }); + }); + describe("when there is an extra column with checkboxes", function () { + beforeEach(function () { columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, { name: 'size', pos: 2 }]; ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)]; - }); it("returns csv for the columns with data", function () { @@ -153,6 +159,13 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'"); }); + describe("when no cells are selected", function () { + it("should return an empty string", function () { + var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, []); + + expect(csvResult).toEqual(''); + }); + }); }); }); }); \ No newline at end of file diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js index 10697e6a..5907c449 100644 --- a/web/regression/javascript/selection/row_selector_spec.js +++ b/web/regression/javascript/selection/row_selector_spec.js @@ -2,26 +2,33 @@ define( ["jquery", "underscore", "slickgrid/slick.grid", + "sources/selection/active_cell_capture", "sources/selection/row_selector", - "slickgrid/slick.rowselectionmodel", + "slickgrid", + 'slickgrid/slick.cellrangedecorator', + 'slickgrid/slick.cellrangeselector', + "slickgrid/slick.cellselectionmodel", ], - function ($, _, SlickGrid, RowSelector, RowSelectionModel, Slick) { + function ($, _, SlickGrid, ActiveCellCapture, RowSelector, Slick) { describe("RowSelector", function () { - var container, data, columnDefinitions, grid, rowSelectionModel; + var container, data, columnDefinitions, grid, cellSelectionModel; beforeEach(function () { container = $("
"); container.height(9999); + container.width(9999); columnDefinitions = [{ id: '1', name: 'some-column-name', - selectable: true + selectable: true, + pos: 0 }, { id: '2', name: 'second column', - selectable: true + selectable: true, + pos: 1 }]; var rowSelector = new RowSelector(); @@ -32,8 +39,10 @@ define( columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); grid = new SlickGrid(container, data, columnDefinitions); - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); + grid.registerPlugin(new ActiveCellCapture()); + cellSelectionModel = new Slick.CellSelectionModel(); + grid.setSelectionModel(cellSelectionModel); + grid.registerPlugin(rowSelector); grid.invalidate(); @@ -53,9 +62,9 @@ define( expect(leftmostColumn.selectable).toBe(false); }); - it("renders a checkbox the leftmost column", function () { - expect(container.find('.sr').length).toBe(11); - expect(container.find('.sr .sc:first-child input[type="checkbox"]').length).toBe(10); + it("renders a span on the leftmost column", function () { + expect(container.find('.slick-row').length).toBe(10); + expect(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]').length).toBe(10); }); it("preserves the other attributes of column definitions", function () { @@ -64,44 +73,44 @@ define( }); describe("selecting rows", function () { - describe("when the user clicks a row header checkbox", function () { + describe("when the user clicks a row header span", function () { it("selects the row", function () { - container.find('.sr .sc:first-child input[type="checkbox"]')[0].click(); + container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstRowToBeSelected(selectedRanges); }); it("checks the checkbox", function () { - container.find('.sr .sc:first-child input[type="checkbox"]')[5].click(); + container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[5].click(); - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[5]) + expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[5]) .is(':checked')).toBeTruthy(); }); }); describe("when the user clicks a row header", function () { it("selects the row", function () { - container.find('.sr .sc:first-child')[0].click(); + container.find('.slick-row .slick-cell:first-child')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstRowToBeSelected(selectedRanges); }); it("checks the checkbox", function () { - container.find('.sr .sc:first-child')[7].click(); + container.find('.slick-row .slick-cell:first-child')[7].click(); - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[7]) + expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[7]) .is(':checked')).toBeTruthy(); }); }); describe("when the user clicks multiple row headers", function () { it("selects another row", function () { - container.find('.sr .sc:first-child')[4].click(); - container.find('.sr .sc:first-child')[0].click(); + container.find('.slick-row .slick-cell:first-child')[4].click(); + container.find('.slick-row .slick-cell:first-child')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expect(selectedRanges.length).toEqual(2); var row1 = selectedRanges[0]; @@ -117,12 +126,12 @@ define( describe("when a column was already selected", function () { beforeEach(function () { var selectedRanges = [new Slick.Range(0, 0, 0, 1)]; - rowSelectionModel.setSelectedRanges(selectedRanges); + cellSelectionModel.setSelectedRanges(selectedRanges); }); it("deselects the column", function () { - container.find('.sr .sc:first-child')[0].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + container.find('.slick-row .slick-cell:first-child')[0].click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstRowToBeSelected(selectedRanges); }) @@ -130,31 +139,31 @@ define( describe("when the row is deselected through setSelectedRanges", function () { beforeEach(function () { - container.find('.sr .sc:first-child')[4].click(); + container.find('.slick-row .slick-cell:first-child')[4].click(); }); it("should uncheck the checkbox", function () { - rowSelectionModel.setSelectedRanges([]); + cellSelectionModel.setSelectedRanges([]); - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[4]) + expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[4]) .is(':checked')).toBeFalsy(); }); }); describe("click a second time", function () { beforeEach(function () { - container.find('.sr .sc:first-child')[1].click(); + container.find('.slick-row .slick-cell:first-child')[1].click(); }); it("unchecks checkbox", function () { - container.find('.sr .sc:first-child')[1].click(); - expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[1]) + container.find('.slick-row .slick-cell:first-child')[1].click(); + expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[1]) .is(':checked')).toBeFalsy(); }); it("unselects the row", function () { - container.find('.sr .sc:first-child')[1].click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + container.find('.slick-row .slick-cell:first-child')[1].click(); + var selectedRanges = cellSelectionModel.getSelectedRanges(); expect(selectedRanges.length).toEqual(0); }) diff --git a/web/regression/javascript/selection/set_staged_rows_spec.js b/web/regression/javascript/selection/set_staged_rows_spec.js index 11e293fd..8949a0f4 100644 --- a/web/regression/javascript/selection/set_staged_rows_spec.js +++ b/web/regression/javascript/selection/set_staged_rows_spec.js @@ -16,6 +16,7 @@ define([ var sqlEditorObj, deleteButton, copyButton; beforeEach(function () { var gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']); + gridSpy.getData.and.returnValue([ {0: 'one', 1: 'two', __temp_PK: '123'}, {0: 'three', 1: 'four', __temp_PK: '456'}, @@ -24,6 +25,11 @@ define([ ]); deleteButton = $(''); copyButton = $(''); + + var selectionSpy = jasmine.createSpyObj('selectionSpy', [ + 'getSelectedRanges', + ]); + sqlEditorObj = { grid: gridSpy, editor: { @@ -32,7 +38,8 @@ define([ staged_rows: {1: [1, 2]} } } - } + }, + selection: selectionSpy, }; $('body').append(deleteButton); $('body').append(copyButton); @@ -63,23 +70,54 @@ define([ }); describe('when getSelectedRows is present in the selection model', function () { + var selectionSpy; beforeEach(function () { - var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']); + selectionSpy = jasmine.createSpyObj('selectionSpy', [ + 'getSelectedRows', + 'setSelectedRows', + 'getSelectedRanges', + ]); selectionSpy.getSelectedRows.and.returnValue([]); - sqlEditorObj.selection = selectionSpy; - SetStagedRows.call(sqlEditorObj, {}, {}); }); - it('should disable the delete row button', function () { - expect($('#btn-delete-row').prop('disabled')).toBeTruthy(); - }); + describe('when nothing is selected', function () { + beforeEach(function () { + selectionSpy.getSelectedRanges.and.returnValue({length: 0}); + sqlEditorObj.selection = selectionSpy; + SetStagedRows.call(sqlEditorObj, {}, {}); + }); - it('should disable the copy row button', function () { - expect($('#btn-copy-row').prop('disabled')).toBeTruthy(); + it('should disable the delete row button', function () { + expect($('#btn-delete-row').prop('disabled')).toBeTruthy(); + }); + + it('should disable the copy row button', function () { + expect($('#btn-copy-row').prop('disabled')).toBeTruthy(); + }); + + it('should clear staged rows', function () { + expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({}); + }); }); - it('should clear staged rows', function () { - expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({}); + describe('when there is a selection', function () { + beforeEach(function () { + selectionSpy.getSelectedRanges.and.returnValue({length: 1}); + sqlEditorObj.selection = selectionSpy; + SetStagedRows.call(sqlEditorObj, {}, {}); + }); + + it('should disable the delete row button', function () { + expect($('#btn-delete-row').prop('disabled')).toBeTruthy(); + }); + + it('should disable the copy row button', function () { + expect($('#btn-copy-row').prop('disabled')).toBeFalsy(); + }); + + it('should clear staged rows', function () { + expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({}); + }); }); }); }); @@ -96,8 +134,9 @@ define([ {0: 'seven', 1: 'eight', __temp_PK: '432'} ]); - var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']); + var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows', 'getSelectedRanges']); selectionSpy.getSelectedRows.and.returnValue([1, 2]); + selectionSpy.getSelectedRanges.and.returnValue({length: 1}); deleteButton = $(''); copyButton = $(''); diff --git a/web/regression/javascript/slickgrid/cell_selector_spec.js b/web/regression/javascript/slickgrid/cell_selector_spec.js new file mode 100644 index 00000000..f1178541 --- /dev/null +++ b/web/regression/javascript/slickgrid/cell_selector_spec.js @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +define(["jquery", + "slickgrid/slick.grid", + "slickgrid/slick.rowselectionmodel", + "sources/slickgrid/cell_selector", + "sources/selection/range_selection_helper" + ], + function ($, SlickGrid, RowSelectionModel, CellSelector, RangeSelectionHelper) { + describe("CellSelector", function () { + var container, columns, cellSelector, data, rowSelectionModel, grid; + beforeEach(function () { + container = $("
"); + container.height(9999); + container.width(9999); + columns = [{ + name: 'some-column-name', + }, { + name: 'second column', + }]; + + cellSelector = new CellSelector(); + + data = []; + for (var i = 0; i < 10; i++) { + data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); + } + grid = new SlickGrid(container, data, columns); + + rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + + grid.registerPlugin(cellSelector); + grid.invalidate(); + + $("body").append(container); + }); + + afterEach(function () { + $("body").find(container).remove(); + }); + + describe("when the user clicks or tabs to a cell", function () { + it("sets the selected range to that cell", function () { + var row = 1, column = 0; + $(container.find(".slick-row .slick-cell.l" + column)[row]).click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toBe(1); + expect(selectedRanges[0].fromCell).toBe(0); + expect(selectedRanges[0].toCell).toBe(0); + expect(selectedRanges[0].fromRow).toBe(1); + expect(selectedRanges[0].toRow).toBe(1); + }); + + it("deselects previously selected ranges", function () { + var row2Range = RangeSelectionHelper.rangeForRow(grid, 2); + var ranges = RangeSelectionHelper.addRange(rowSelectionModel.getSelectedRanges(), + row2Range); + rowSelectionModel.setSelectedRanges(ranges); + + var row = 4, column = 1; + $(container.find(".slick-row .slick-cell.l" + column)[row]).click(); + + expect(RangeSelectionHelper.isRangeSelected(rowSelectionModel.getSelectedRanges(), row2Range)) + .toBe(false); + }); + }); + }); + }); \ No newline at end of file diff --git a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js new file mode 100644 index 00000000..b61e5aff --- /dev/null +++ b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js @@ -0,0 +1,145 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +define([ + 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event', + 'sources/selection/clipboard', + 'sources/selection/range_selection_helper', + 'slickgrid', + 'slickgrid/slick.cellrangedecorator', + 'slickgrid/slick.cellrangeselector', + 'slickgrid/slick.cellselectionmodel', + ], +function (handleQueryOutputKeyboardEvent, clipboard, RangeSelectionHelper) { + var Slick = window.Slick; + + describe('#handleQueryOutputKeyboardEvent', function () { + var event, view, grid, slickEvent; + beforeEach(function () { + event = { + shiftKey: false, + ctrlKey: false, + metaKey: false, + which: -1, + keyCode: -1, + preventDefault: jasmine.createSpy('preventDefault') + }; + + var data = [['', '0,0-cell-content', '0,1-cell-content'], + ['', '1,0-cell-content', '1,1-cell-content'], + ['', '2,0-cell-content', '2,1-cell-content']]; + var columnDefinitions = [{name: 'checkboxColumn'}, {pos: 1, name: 'firstColumn'}, { + pos: 2, + name: 'secondColumn' + }]; + grid = new Slick.Grid($('
'), data, columnDefinitions); + grid.setSelectionModel(new Slick.CellSelectionModel()); + + slickEvent = { + grid: grid + }; + + view = {}; + spyOn(clipboard, 'copyTextToClipboard'); + }); + + describe("when a range is selected", function () { + beforeEach(function () { + grid.getSelectionModel().setSelectedRanges([ + RangeSelectionHelper.rangeForRow(grid, 0), + RangeSelectionHelper.rangeForRow(grid, 2), + ]); + }); + + describe("pressing Command + C", function () { + beforeEach(function () { + event.metaKey = true; + event.keyCode = 67; + }); + + it("copies the cell content to the clipboard", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(clipboard.copyTextToClipboard).toHaveBeenCalledWith("'0,0-cell-content','0,1-cell-content'\n'2,0-cell-content','2,1-cell-content'"); + }); + }); + + describe("pressing Ctrl + C", function () { + beforeEach(function () { + event.ctrlKey = true; + event.keyCode = 67; + }); + + it("copies the cell content to the clipboard", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(clipboard.copyTextToClipboard).toHaveBeenCalledWith("'0,0-cell-content','0,1-cell-content'\n'2,0-cell-content','2,1-cell-content'"); + }); + }); + + describe("pressing Command + A", function () { + beforeEach(function () { + event.metaKey = true; + event.keyCode = 65; + }); + + it("selects the entire grid to ranges", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy(); + expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1); + }); + }); + + describe("pressing Ctrl + A", function () { + beforeEach(function () { + event.ctrlKey = true; + event.keyCode = 65; + }); + + it("selects the entire grid to ranges", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy(); + expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1); + }); + }); + }); + + describe("when no ranges are selected", function () { + describe("pressing Command + A", function () { + beforeEach(function () { + event.metaKey = true; + event.keyCode = 65; + }); + + it("selects the entire grid to ranges", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy(); + expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1); + }); + }); + + describe("pressing Ctrl + A", function () { + beforeEach(function () { + event.ctrlKey = true; + event.keyCode = 65; + }); + + it("selects the entire grid to ranges", function () { + handleQueryOutputKeyboardEvent(event, slickEvent); + + expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy(); + expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/web/regression/javascript/test-main.js b/web/regression/javascript/test-main.js index 421bb178..e826cca7 100644 --- a/web/regression/javascript/test-main.js +++ b/web/regression/javascript/test-main.js @@ -37,6 +37,9 @@ require.config({ 'slickgrid': sourcesDir + 'vendor/slickgrid/slick.core', 'slickgrid/slick.grid': sourcesDir + 'vendor/slickgrid/slick.grid', 'slickgrid/slick.rowselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.rowselectionmodel', + 'slickgrid/slick.cellselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.cellselectionmodel', + 'slickgrid/slick.cellrangedecorator': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangedecorator', + 'slickgrid/slick.cellrangeselector': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangeselector', 'translations': '/base/regression/javascript/fake_translations', 'sources': sourcesDir + 'js', 'browser': '/base/pgadmin/browser/static/js' diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index c50dd310..cf559c44 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -159,12 +159,14 @@ def create_table(server, db_name, table_name): connection.set_isolation_level(0) pg_cursor = connection.cursor() pg_cursor.execute( - '''CREATE TABLE "%s" (some_column VARCHAR, value NUMERIC)''' % + '''CREATE TABLE "%s" (some_column VARCHAR, value NUMERIC, details VARCHAR)''' % table_name) pg_cursor.execute( - '''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name) + '''INSERT INTO "%s" VALUES ('Some-Name', 6, 'some info')''' % table_name) pg_cursor.execute( - '''INSERT INTO "%s" VALUES ('Some-Other-Name', 22)''' % table_name) + '''INSERT INTO "%s" VALUES ('Some-Other-Name', 22, 'some other info')''' % table_name) + pg_cursor.execute( + '''INSERT INTO "%s" VALUES ('Yet-Another-Name', 14, 'cool info')''' % table_name) connection.set_isolation_level(old_isolation_level) connection.commit()