diff --git a/web/pgadmin/feature_tests/connect_to_server_feature_test.py b/web/pgadmin/feature_tests/connect_to_server_feature_test.py index 3999c6e0..e8de161a 100644 --- a/web/pgadmin/feature_tests/connect_to_server_feature_test.py +++ b/web/pgadmin/feature_tests/connect_to_server_feature_test.py @@ -69,7 +69,7 @@ class ConnectsToServerFeatureTest(BaseFeatureTest): self.page.find_by_xpath("//button[contains(.,'Save')]").click() def _tables_node_expandable(self): - self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_server(self.server['name']) self.page.toggle_open_tree_item('Databases') self.page.toggle_open_tree_item('acceptance_test_db') self.page.toggle_open_tree_item('Schemas') 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 08fc5556..ac667001 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 @@ -55,6 +55,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self._copies_columns() self._copies_row_using_keyboard_shortcut() self._copies_column_using_keyboard_shortcut() + self._copies_rectangular_selection() self._shift_resizes_rectangular_selection() self._shift_resizes_column_selection() @@ -99,6 +100,19 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): 'Yet-Another-Name'""", pyperclip.paste()) + def _copies_rectangular_selection(self): + pyperclip.copy("old clipboard contents") + + topLeftCell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") + bottomRightCell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") + + ActionChains(self.page.driver).click_and_hold(topLeftCell).move_to_element(bottomRightCell).release(bottomRightCell).perform() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("""'Some-Other-Name','22' +'Yet-Another-Name','14'""", pyperclip.paste()) + def _shift_resizes_rectangular_selection(self): pyperclip.copy("old clipboard contents") diff --git a/web/pgadmin/feature_tests/table_ddl_feature_test.py b/web/pgadmin/feature_tests/table_ddl_feature_test.py index a29995e6..9fb90d66 100644 --- a/web/pgadmin/feature_tests/table_ddl_feature_test.py +++ b/web/pgadmin/feature_tests/table_ddl_feature_test.py @@ -33,7 +33,7 @@ class TableDdlFeatureTest(BaseFeatureTest): def runTest(self): test_utils.create_table(self.server, "acceptance_test_db", "test_table") - self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_server(self.server['name']) self.page.toggle_open_tree_item('Databases') self.page.toggle_open_tree_item('acceptance_test_db') self.page.toggle_open_tree_item('Schemas') diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py index d59e8ac3..4fd07833 100644 --- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py +++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py @@ -88,7 +88,7 @@ class CheckForXssFeatureTest(BaseFeatureTest): self.page.find_by_xpath("//button[contains(.,'Save')]").click() def _tables_node_expandable(self): - self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_server(self.server['name']) self.page.toggle_open_tree_item('Databases') self.page.toggle_open_tree_item('acceptance_test_db') self.page.toggle_open_tree_item('Schemas') diff --git a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py index 17bc7bf5..959b2c19 100644 --- a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py +++ b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py @@ -58,7 +58,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): self.page.find_by_xpath("//button[contains(.,'Save')]").click() def _function_node_expandable(self): - self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_server(self.server['name']) self.page.toggle_open_tree_item('Databases') self.page.toggle_open_tree_item('postgres') self.page.toggle_open_tree_item('Schemas') diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js index 2923a796..b03c8cf3 100644 --- a/web/pgadmin/static/js/selection/column_selector.js +++ b/web/pgadmin/static/js/selection/column_selector.js @@ -52,7 +52,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func if (rangeSelectionHelper.isRangeSelected(ranges, columnRange)) { newRanges = rangeSelectionHelper.removeRange(ranges, columnRange); } else { - if (rangeSelectionHelper.areAllRangesColumns(ranges, grid)) { + if (rangeSelectionHelper.areAllRangesSingleColumns(ranges, grid)) { newRanges = rangeSelectionHelper.addRange(ranges, columnRange); } else { newRanges = [columnRange]; diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js index 6c69c390..3d121f4f 100644 --- a/web/pgadmin/static/js/selection/range_selection_helper.js +++ b/web/pgadmin/static/js/selection/range_selection_helper.js @@ -23,14 +23,13 @@ define(['slickgrid'], function () { return ranges; }; - var areAllRangesRows = function (ranges, grid) { + var areAllRangesSingleRows = function (ranges, grid) { return _.every(ranges, function (range) { - return range.fromRow == range.toRow && - range.fromCell == 1 && range.toCell == grid.getColumns().length - 1 + return range.fromRow == range.toRow && rangeHasCompleteRows(grid, range); }) }; - var areAllRangesColumns = function (ranges, grid) { + var areAllRangesSingleColumns = function (ranges, grid) { return _.every(ranges, function (range) { return range.fromCell == range.toCell && range.fromRow == 0 && range.toRow == grid.getDataLength() - 1 @@ -39,7 +38,7 @@ define(['slickgrid'], function () { var rangeForRow = function (grid, rowId) { var columnDefinitions = grid.getColumns(); - if(isFirstColumnData(columnDefinitions)) { + if (isFirstColumnData(columnDefinitions)) { return new Slick.Range(rowId, 0, rowId, grid.getColumns().length - 1); } return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1); @@ -63,6 +62,28 @@ define(['slickgrid'], function () { return !_.isUndefined(columnDefinitions[0].pos); }; + var areAllRangesCompleteRows = function (grid, ranges) { + return _.every(ranges, function (range) { + return rangeHasCompleteRows(grid, range); + }) + }; + + var getIndexesOfCompleteRows = function (grid, ranges) { + var indexArray = []; + ranges.forEach(function (range) { + if (rangeHasCompleteRows(grid, range)) + indexArray = _.union(indexArray, _.range(range.fromRow, range.toRow + 1)); + }); + + return indexArray; + }; + + function rangeHasCompleteRows(grid, range) { + var firstDataColumn = isFirstColumnData(grid.getColumns()) ? 0 : 1; + return range.fromCell == firstDataColumn && + range.toCell == grid.getColumns().length - 1; + } + function selectAll(grid) { var range = getRangeOfWholeGrid(grid); var selectionModel = grid.getSelectionModel(); @@ -74,13 +95,15 @@ define(['slickgrid'], function () { addRange: addRange, removeRange: removeRange, isRangeSelected: isRangeSelected, - areAllRangesRows: areAllRangesRows, - areAllRangesColumns: areAllRangesColumns, + areAllRangesSingleRows: areAllRangesSingleRows, + areAllRangesSingleColumns: areAllRangesSingleColumns, + areAllRangesCompleteRows: areAllRangesCompleteRows, rangeForRow: rangeForRow, rangeForColumn: rangeForColumn, isEntireGridSelected: isEntireGridSelected, getRangeOfWholeGrid: getRangeOfWholeGrid, isFirstColumnData: isFirstColumnData, selectAll: selectAll, + getIndexesOfCompleteRows: getIndexesOfCompleteRows, } }); \ 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 8124b5cb..9e539353 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -44,7 +44,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func if (rangeSelectionHelper.isRangeSelected(ranges, rowRange)) { newRanges = rangeSelectionHelper.removeRange(ranges, rowRange); } else { - if (rangeSelectionHelper.areAllRangesRows(ranges, grid)) { + if (rangeSelectionHelper.areAllRangesSingleRows(ranges, grid)) { newRanges = rangeSelectionHelper.addRange(ranges, rowRange); } else { newRanges = [rowRange]; diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 15b78d9c..46d50156 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -89,6 +89,31 @@ class PgadminPage: def toggle_open_tree_item(self, tree_item_text): self.find_by_xpath("//*[@id='tree']//*[.='" + tree_item_text + "']/../*[@class='aciTreeButton']").click() + def toggle_open_server(self, tree_item_text): + def check_for_password_dialog_or_tree_open(driver): + try: + dialog = driver.find_element_by_id("frmPassword") + except WebDriverException: + dialog = None + + try: + database_node = driver.find_element_by_xpath("//*[@id='tree']//*[.='Databases']/../*[@class='aciTreeButton']") + except WebDriverException: + database_node = None + + return dialog is not None or database_node is not None + + self.toggle_open_tree_item(tree_item_text) + self._wait_for("Waiting for password dialog or tree to open", check_for_password_dialog_or_tree_open) + + try: + self.driver.find_element_by_id("frmPassword") + # Enter password here if needed + self.click_modal_ok() + except WebDriverException: + return + + def find_by_xpath(self, xpath): return self.wait_for_element(lambda driver: driver.find_element_by_xpath(xpath)) diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js index 4526b602..2b0c645f 100644 --- a/web/regression/javascript/selection/column_selector_spec.js +++ b/web/regression/javascript/selection/column_selector_spec.js @@ -59,7 +59,7 @@ define( selectable: false, pos: 3 } - ] + ]; }); describe("when a column is not selectable", function () { diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js index 1c2f7898..05d1208d 100644 --- a/web/regression/javascript/selection/grid_selector_spec.js +++ b/web/regression/javascript/selection/grid_selector_spec.js @@ -13,10 +13,12 @@ define(["jquery", container.height(9999); columns = [{ id: '1', - name: 'some-column-name' + name: 'some-column-name', + pos: 0 }, { id: '2', - name: 'second column' + name: 'second column', + pos: 1 }]; gridSelector = new GridSelector(); diff --git a/web/regression/javascript/selection/range_selection_helper_spec.js b/web/regression/javascript/selection/range_selection_helper_spec.js new file mode 100644 index 00000000..cc81189b --- /dev/null +++ b/web/regression/javascript/selection/range_selection_helper_spec.js @@ -0,0 +1,93 @@ +define([ + 'jquery', + 'slickgrid/slick.grid', + 'sources/selection/range_selection_helper' +], function ($, SlickGrid, RangeSelectionHelper) { + describe("RangeSelectionHelper utility functions", function () { + var grid; + beforeEach(function () { + var container, data, columns, options; + container = $("
"); + container.height(9999); + + columns = [{ + id: '1', + name: 'some-column-name', + pos: 0 + }, { + id: 'second-column-id', + name: 'second column', + pos: 1 + }]; + + 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, options); + grid.invalidate(); + }); + + describe("#getIndexesOfCompleteRows", function () { + describe("when selected ranges are not rows", function () { + it("returns an empty array", function () { + var rowlessRanges = [RangeSelectionHelper.rangeForColumn(grid, 1)]; + + expect(RangeSelectionHelper.getIndexesOfCompleteRows(grid, rowlessRanges)) + .toEqual([]); + }); + }); + describe("when selected range", function () { + describe("is a single row", function () { + it("returns an array with one index", function () { + var singleRowRange = [RangeSelectionHelper.rangeForRow(grid, 1)]; + + expect(RangeSelectionHelper.getIndexesOfCompleteRows(grid, singleRowRange)) + .toEqual([1]); + }); + }); + + describe("is multiple rows", function () { + it("returns an array of each row's index", function () { + var multipleRowRange = [ + RangeSelectionHelper.rangeForRow(grid, 0), + RangeSelectionHelper.rangeForRow(grid, 3), + RangeSelectionHelper.rangeForRow(grid, 2), + ]; + + var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange); + indexesOfCompleteRows.sort(); + expect(indexesOfCompleteRows).toEqual([0, 2, 3]); + }); + }); + + describe("contains a multi row selection", function () { + it("returns an array of each individual row's index", function () { + var multipleRowRange = [ + new Slick.Range(3, 0, 5, 1) + ]; + + var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange); + indexesOfCompleteRows.sort(); + expect(indexesOfCompleteRows).toEqual([3, 4, 5]); + }); + + describe("and also contains a selection that is not a row", function () { + it("returns an array of only the complete rows' indexes", function () { + var multipleRowRange = [ + new Slick.Range(8, 1, 9, 1), + new Slick.Range(3, 0, 5, 1) + ]; + + var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange); + indexesOfCompleteRows.sort(); + expect(indexesOfCompleteRows).toEqual([3, 4, 5]); + }); + }); + }); + }); + }); + }); + +}); diff --git a/web/regression/javascript/test-main.js b/web/regression/javascript/test-main.js index e826cca7..ba200a35 100644 --- a/web/regression/javascript/test-main.js +++ b/web/regression/javascript/test-main.js @@ -67,6 +67,24 @@ require.config({ ], "exports": 'window.Slick.RowSelectionModel' }, + "slickgrid/slick.cellrangedecorator": { + "deps": [ + "jquery" + ], + "exports": 'window.Slick.RowRangeDecorator' + }, + "slickgrid/slick.cellrangeselector": { + "deps": [ + "jquery", "slickgrid/slick.cellrangedecorator" + ], + "exports": 'window.Slick.CellRangeSelector' + }, + "slickgrid/slick.cellselectionmodel": { + "deps": [ + "jquery", "slickgrid/slick.cellrangeselector" + ], + "exports": 'window.Slick.CellSelectionModel' + }, "backbone": { "deps": ['underscore', 'jquery'], "exports": 'Backbone' diff --git a/web/regression/requirements.txt b/web/regression/requirements.txt index 6babf987..5959a183 100644 --- a/web/regression/requirements.txt +++ b/web/regression/requirements.txt @@ -1,5 +1,5 @@ pyperclip~=1.5.27 -selenium==3.3.1 +selenium==3.3.3 testscenarios==0.5.0 testtools==2.0.0 traceback2==1.4.0