mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Allow column or row selection in the query tool. Fixes #2216
This commit is contained in:
parent
c35dbc7211
commit
d663d553c5
BIN
docs/en_US/images/query_output_data.png
Executable file → Normal file
BIN
docs/en_US/images/query_output_data.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 51 KiB |
@ -0,0 +1,76 @@
|
|||||||
|
import pyperclip
|
||||||
|
import time
|
||||||
|
|
||||||
|
from selenium.webdriver import ActionChains
|
||||||
|
|
||||||
|
from regression.python_test_utils import test_utils
|
||||||
|
from regression.feature_utils.base_feature_test import BaseFeatureTest
|
||||||
|
|
||||||
|
|
||||||
|
class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
|
||||||
|
"""
|
||||||
|
Tests various ways to copy data from the query results grid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
scenarios = [
|
||||||
|
("Test Copying Query Results", dict())
|
||||||
|
]
|
||||||
|
|
||||||
|
def before(self):
|
||||||
|
connection = test_utils.get_db_connection(self.server['db'],
|
||||||
|
self.server['username'],
|
||||||
|
self.server['db_password'],
|
||||||
|
self.server['host'],
|
||||||
|
self.server['port'])
|
||||||
|
test_utils.drop_database(connection, "acceptance_test_db")
|
||||||
|
test_utils.create_database(self.server, "acceptance_test_db")
|
||||||
|
test_utils.create_table(self.server, "acceptance_test_db", "test_table")
|
||||||
|
self.page.add_server(self.server)
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
self.page.toggle_open_tree_item(self.server['name'])
|
||||||
|
self.page.toggle_open_tree_item('Databases')
|
||||||
|
self.page.toggle_open_tree_item('acceptance_test_db')
|
||||||
|
time.sleep(5)
|
||||||
|
self.page.find_by_partial_link_text("Tools").click()
|
||||||
|
self.page.find_by_partial_link_text("Query Tool").click()
|
||||||
|
self.page.click_tab('Query-1')
|
||||||
|
time.sleep(5)
|
||||||
|
ActionChains(self.page.driver).send_keys("SELECT * FROM test_table").perform()
|
||||||
|
self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
|
||||||
|
self.page.find_by_id("btn-flash").click()
|
||||||
|
|
||||||
|
self._copies_rows()
|
||||||
|
self._copies_columns()
|
||||||
|
|
||||||
|
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("//*[@id='btn-copy-row']").click()
|
||||||
|
|
||||||
|
self.assertEqual("'Some-Name','6'",
|
||||||
|
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("//*[@id='btn-copy-row']").click()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
"""'Some-Name'
|
||||||
|
'Some-Other-Name'""",
|
||||||
|
pyperclip.paste())
|
||||||
|
|
||||||
|
def after(self):
|
||||||
|
self.page.close_query_tool()
|
||||||
|
self.page.remove_server(self.server)
|
||||||
|
|
||||||
|
connection = test_utils.get_db_connection(self.server['db'],
|
||||||
|
self.server['username'],
|
||||||
|
self.server['db_password'],
|
||||||
|
self.server['host'],
|
||||||
|
self.server['port'])
|
||||||
|
test_utils.drop_database(connection, "acceptance_test_db")
|
92
web/pgadmin/static/js/selection/column_selector.js
Normal file
92
web/pgadmin/static/js/selection/column_selector.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) {
|
||||||
|
var ColumnSelector = function () {
|
||||||
|
var init = function (grid) {
|
||||||
|
grid.onHeaderClick.subscribe(function (event, eventArgument) {
|
||||||
|
var column = eventArgument.column;
|
||||||
|
|
||||||
|
if (column.selectable !== false) {
|
||||||
|
|
||||||
|
if (!clickedCheckbox(event)) {
|
||||||
|
var $checkbox = $("[data-id='checkbox-" + column.id + "']");
|
||||||
|
toggleCheckbox($checkbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRanges(grid, column.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
grid.getSelectionModel().onSelectedRangesChanged
|
||||||
|
.subscribe(handleSelectedRangesChanged.bind(null, grid));
|
||||||
|
};
|
||||||
|
|
||||||
|
var handleSelectedRangesChanged = function (grid, event, ranges) {
|
||||||
|
$('[data-cell-type="column-header-row"] input:checked')
|
||||||
|
.each(function (index, checkbox) {
|
||||||
|
var $checkbox = $(checkbox);
|
||||||
|
var columnIndex = grid.getColumnIndex($checkbox.data('column-id'));
|
||||||
|
var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, rangeSelectionHelper.rangeForColumn(grid, columnIndex));
|
||||||
|
if (!isStillSelected) {
|
||||||
|
toggleCheckbox($checkbox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateRanges = function (grid, columnId) {
|
||||||
|
var selectionModel = grid.getSelectionModel();
|
||||||
|
var ranges = selectionModel.getSelectedRanges();
|
||||||
|
|
||||||
|
var columnIndex = grid.getColumnIndex(columnId);
|
||||||
|
|
||||||
|
var columnRange = rangeSelectionHelper.rangeForColumn(grid, columnIndex);
|
||||||
|
var newRanges;
|
||||||
|
if (rangeSelectionHelper.isRangeSelected(ranges, columnRange)) {
|
||||||
|
newRanges = rangeSelectionHelper.removeRange(ranges, columnRange);
|
||||||
|
} else {
|
||||||
|
if (rangeSelectionHelper.areAllRangesColumns(ranges, grid)) {
|
||||||
|
newRanges = rangeSelectionHelper.addRange(ranges, columnRange);
|
||||||
|
} else {
|
||||||
|
newRanges = [columnRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionModel.setSelectedRanges(newRanges);
|
||||||
|
};
|
||||||
|
|
||||||
|
var clickedCheckbox = function (e) {
|
||||||
|
return e.target.type == "checkbox"
|
||||||
|
};
|
||||||
|
|
||||||
|
var toggleCheckbox = function (checkbox) {
|
||||||
|
if (checkbox.prop("checked")) {
|
||||||
|
checkbox.prop("checked", false)
|
||||||
|
} else {
|
||||||
|
checkbox.prop("checked", true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
|
||||||
|
return _.map(columnDefinitions, function (columnDefinition) {
|
||||||
|
if (columnDefinition.selectable !== false) {
|
||||||
|
var name =
|
||||||
|
"<span data-cell-type='column-header-row' " +
|
||||||
|
" data-test='output-column-header'>" +
|
||||||
|
" <input data-id='checkbox-" + columnDefinition.id + "' " +
|
||||||
|
" data-column-id='" + columnDefinition.id + "' " +
|
||||||
|
" type='checkbox'/>" +
|
||||||
|
" <span class='column-description'>" + columnDefinition.name + "</span>" +
|
||||||
|
"</span>";
|
||||||
|
return _.extend(columnDefinition, {
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return columnDefinition;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend(this, {
|
||||||
|
"init": init,
|
||||||
|
"getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return ColumnSelector;
|
||||||
|
});
|
52
web/pgadmin/static/js/selection/copy_data.js
Normal file
52
web/pgadmin/static/js/selection/copy_data.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'underscore',
|
||||||
|
'sources/selection/clipboard',
|
||||||
|
'sources/selection/range_selection_helper',
|
||||||
|
'sources/selection/range_boundary_navigator'],
|
||||||
|
function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
|
||||||
|
var copyData = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var grid = self.slickgrid;
|
||||||
|
var columnDefinitions = grid.getColumns();
|
||||||
|
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
|
||||||
|
var data = grid.getData();
|
||||||
|
var rows = grid.getSelectedRows();
|
||||||
|
|
||||||
|
|
||||||
|
if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) {
|
||||||
|
self.copied_rows = rows.map(function (rowIndex) {
|
||||||
|
return data[rowIndex];
|
||||||
|
});
|
||||||
|
setPasteRowButtonEnablement(self.can_edit, true);
|
||||||
|
} else {
|
||||||
|
self.copied_rows = [];
|
||||||
|
setPasteRowButtonEnablement(self.can_edit, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges);
|
||||||
|
if (csvText) {
|
||||||
|
clipboard.copyTextToClipboard(csvText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
|
||||||
|
if (canEditFlag) {
|
||||||
|
$("#btn-paste-row").prop('disabled', !isEnabled);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var allTheRangesAreFullRows = function (ranges, columnDefinitions) {
|
||||||
|
var colRangeBounds = ranges.map(function (range) {
|
||||||
|
return [range.fromCell, range.toCell];
|
||||||
|
});
|
||||||
|
|
||||||
|
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 copyData;
|
||||||
|
});
|
79
web/pgadmin/static/js/selection/grid_selector.js
Normal file
79
web/pgadmin/static/js/selection/grid_selector.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'],
|
||||||
|
function ($, ColumnSelector, RowSelector) {
|
||||||
|
var Slick = window.Slick;
|
||||||
|
|
||||||
|
var GridSelector = function (columnDefinitions) {
|
||||||
|
var rowSelector = new RowSelector(columnDefinitions);
|
||||||
|
var columnSelector = new ColumnSelector(columnDefinitions);
|
||||||
|
|
||||||
|
var init = function (grid) {
|
||||||
|
this.grid = grid;
|
||||||
|
grid.onHeaderClick.subscribe(function (event, eventArguments) {
|
||||||
|
if (eventArguments.column.selectAllOnClick) {
|
||||||
|
toggleSelectAll(grid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.getSelectionModel().onSelectedRangesChanged
|
||||||
|
.subscribe(handleSelectedRangesChanged.bind(null, grid));
|
||||||
|
grid.registerPlugin(rowSelector);
|
||||||
|
grid.registerPlugin(columnSelector);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
|
||||||
|
columnDefinitions = columnSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
|
||||||
|
columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
|
||||||
|
|
||||||
|
columnDefinitions[0].selectAllOnClick = true;
|
||||||
|
columnDefinitions[0].name = '<input type="checkbox" data-id="checkbox-select-all" ' +
|
||||||
|
'title="Select/Deselect All"/>' + columnDefinitions[0].name;
|
||||||
|
return columnDefinitions;
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectAll(grid) {
|
||||||
|
if (isEntireGridSelected(grid)) {
|
||||||
|
deselect(grid);
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return GridSelector;
|
||||||
|
});
|
111
web/pgadmin/static/js/selection/range_boundary_navigator.js
Normal file
111
web/pgadmin/static/js/selection/range_boundary_navigator.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
define(['sources/selection/range_selection_helper'], function (RangeSelectionHelper) {
|
||||||
|
return {
|
||||||
|
getUnion: function (allRanges) {
|
||||||
|
if (_.isEmpty(allRanges)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
allRanges.sort(firstElementNumberComparator);
|
||||||
|
var unionedRanges = [allRanges[0]];
|
||||||
|
|
||||||
|
allRanges.forEach(function (range) {
|
||||||
|
var maxBeginningOfRange = _.last(unionedRanges);
|
||||||
|
if (isStartInsideRange(range, maxBeginningOfRange)) {
|
||||||
|
if (!isEndInsideRange(range, maxBeginningOfRange)) {
|
||||||
|
maxBeginningOfRange[1] = range[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unionedRanges.push(range);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return unionedRanges;
|
||||||
|
|
||||||
|
function firstElementNumberComparator(a, b) {
|
||||||
|
return a[0] - b[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartInsideRange(range, surroundingRange) {
|
||||||
|
return range[0] <= surroundingRange[1] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEndInsideRange(range, surroundingRange) {
|
||||||
|
return range[1] <= surroundingRange[1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mapDimensionBoundaryUnion: function (unionedDimensionBoundaries, iteratee) {
|
||||||
|
var mapResult = [];
|
||||||
|
unionedDimensionBoundaries.forEach(function (subrange) {
|
||||||
|
for (var index = subrange[0]; index <= subrange[1]; index += 1) {
|
||||||
|
mapResult.push(iteratee(index));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mapResult;
|
||||||
|
},
|
||||||
|
|
||||||
|
mapOver2DArray: function (rowRangeBounds, colRangeBounds, processCell, rowCollector) {
|
||||||
|
var unionedRowRanges = this.getUnion(rowRangeBounds);
|
||||||
|
var unionedColRanges = this.getUnion(colRangeBounds);
|
||||||
|
|
||||||
|
return this.mapDimensionBoundaryUnion(unionedRowRanges, function (rowId) {
|
||||||
|
var rowData = this.mapDimensionBoundaryUnion(unionedColRanges, function (colId) {
|
||||||
|
return processCell(rowId, colId);
|
||||||
|
});
|
||||||
|
return rowCollector(rowData);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
rangesToCsv: function (data, columnDefinitions, selectedRanges) {
|
||||||
|
var rowRangeBounds = selectedRanges.map(function (range) {
|
||||||
|
return [range.fromRow, range.toRow];
|
||||||
|
});
|
||||||
|
var colRangeBounds = selectedRanges.map(function (range) {
|
||||||
|
return [range.fromCell, range.toCell];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!RangeSelectionHelper.isFirstColumnData(columnDefinitions)) {
|
||||||
|
colRangeBounds = this.removeFirstColumn(colRangeBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
var csvRows = this.mapOver2DArray(rowRangeBounds, colRangeBounds, this.csvCell.bind(this, data, columnDefinitions), function (rowData) {
|
||||||
|
return rowData.join(',');
|
||||||
|
});
|
||||||
|
return csvRows.join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFirstColumn: function (colRangeBounds) {
|
||||||
|
var unionedColRanges = this.getUnion(colRangeBounds);
|
||||||
|
|
||||||
|
var firstSubrangeStartsAt0 = function () {
|
||||||
|
return unionedColRanges[0][0] == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
function firstSubrangeIsJustFirstColumn() {
|
||||||
|
return unionedColRanges[0][1] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstSubrangeStartsAt0()) {
|
||||||
|
if (firstSubrangeIsJustFirstColumn()) {
|
||||||
|
unionedColRanges.shift();
|
||||||
|
} else {
|
||||||
|
unionedColRanges[0][0] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unionedColRanges;
|
||||||
|
},
|
||||||
|
|
||||||
|
csvCell: function (data, columnDefinitions, rowId, colId) {
|
||||||
|
var val = data[rowId][columnDefinitions[colId].pos];
|
||||||
|
|
||||||
|
if (val && _.isObject(val)) {
|
||||||
|
val = "'" + JSON.stringify(val) + "'";
|
||||||
|
} else if (val && typeof val != "number" && typeof val != "boolean") {
|
||||||
|
val = "'" + val.toString() + "'";
|
||||||
|
} else if (_.isNull(val) || _.isUndefined(val)) {
|
||||||
|
val = '';
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
78
web/pgadmin/static/js/selection/range_selection_helper.js
Normal file
78
web/pgadmin/static/js/selection/range_selection_helper.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
define(['slickgrid'], function () {
|
||||||
|
var Slick = window.Slick;
|
||||||
|
|
||||||
|
var isSameRange = function (range, otherRange) {
|
||||||
|
return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell &&
|
||||||
|
range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow;
|
||||||
|
};
|
||||||
|
|
||||||
|
var isRangeSelected = function (selectedRanges, range) {
|
||||||
|
return _.any(selectedRanges, function (selectedRange) {
|
||||||
|
return isSameRange(selectedRange, range)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var removeRange = function (selectedRanges, range) {
|
||||||
|
return _.filter(selectedRanges, function (selectedRange) {
|
||||||
|
return !(isSameRange(selectedRange, range))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var addRange = function (ranges, range) {
|
||||||
|
ranges.push(range);
|
||||||
|
return ranges;
|
||||||
|
};
|
||||||
|
|
||||||
|
var areAllRangesRows = function (ranges, grid) {
|
||||||
|
return _.every(ranges, function (range) {
|
||||||
|
return range.fromRow == range.toRow &&
|
||||||
|
range.fromCell == 1 && range.toCell == grid.getColumns().length - 1
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var areAllRangesColumns = function (ranges, grid) {
|
||||||
|
return _.every(ranges, function (range) {
|
||||||
|
return range.fromCell == range.toCell &&
|
||||||
|
range.fromRow == 0 && range.toRow == grid.getDataLength() - 1
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var rangeForRow = function (grid, rowId) {
|
||||||
|
var columnDefinitions = grid.getColumns();
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
function rangeForColumn(grid, columnIndex) {
|
||||||
|
return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex)
|
||||||
|
};
|
||||||
|
|
||||||
|
var getRangeOfWholeGrid = function (grid) {
|
||||||
|
return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
var isEntireGridSelected = function (grid) {
|
||||||
|
var selectionModel = grid.getSelectionModel();
|
||||||
|
var selectedRanges = selectionModel.getSelectedRanges();
|
||||||
|
return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid));
|
||||||
|
};
|
||||||
|
|
||||||
|
var isFirstColumnData = function (columnDefinitions) {
|
||||||
|
return !_.isUndefined(columnDefinitions[0].pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
addRange: addRange,
|
||||||
|
removeRange: removeRange,
|
||||||
|
isRangeSelected: isRangeSelected,
|
||||||
|
areAllRangesRows: areAllRangesRows,
|
||||||
|
areAllRangesColumns: areAllRangesColumns,
|
||||||
|
rangeForRow: rangeForRow,
|
||||||
|
rangeForColumn: rangeForColumn,
|
||||||
|
isEntireGridSelected: isEntireGridSelected,
|
||||||
|
getRangeOfWholeGrid: getRangeOfWholeGrid,
|
||||||
|
isFirstColumnData: isFirstColumnData
|
||||||
|
}
|
||||||
|
});
|
85
web/pgadmin/static/js/selection/row_selector.js
Normal file
85
web/pgadmin/static/js/selection/row_selector.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) {
|
||||||
|
var RowSelector = function () {
|
||||||
|
var Slick = window.Slick;
|
||||||
|
|
||||||
|
var gridEventBus = new Slick.EventHandler();
|
||||||
|
|
||||||
|
var init = function (grid) {
|
||||||
|
grid.getSelectionModel()
|
||||||
|
.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid));
|
||||||
|
gridEventBus
|
||||||
|
.subscribe(grid.onClick, handleClick.bind(null, grid))
|
||||||
|
};
|
||||||
|
|
||||||
|
var handleClick = function (grid, event, args) {
|
||||||
|
if (grid.getColumns()[args.cell].id === 'row-header-column') {
|
||||||
|
if (event.target.type != "checkbox") {
|
||||||
|
var checkbox = $(event.target).find('input[type="checkbox"]');
|
||||||
|
toggleCheckbox($(checkbox));
|
||||||
|
}
|
||||||
|
updateRanges(grid, args.row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleSelectedRangesChanged = function (grid, event, ranges) {
|
||||||
|
$('[data-cell-type="row-header-checkbox"]:checked')
|
||||||
|
.each(function (index, checkbox) {
|
||||||
|
var $checkbox = $(checkbox);
|
||||||
|
var row = parseInt($checkbox.data('row'));
|
||||||
|
var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges,
|
||||||
|
rangeSelectionHelper.rangeForRow(grid, row));
|
||||||
|
if (!isStillSelected) {
|
||||||
|
toggleCheckbox($checkbox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateRanges = function (grid, rowId) {
|
||||||
|
var selectionModel = grid.getSelectionModel();
|
||||||
|
var ranges = selectionModel.getSelectedRanges();
|
||||||
|
|
||||||
|
var rowRange = rangeSelectionHelper.rangeForRow(grid, rowId);
|
||||||
|
|
||||||
|
var newRanges;
|
||||||
|
if (rangeSelectionHelper.isRangeSelected(ranges, rowRange)) {
|
||||||
|
newRanges = rangeSelectionHelper.removeRange(ranges, rowRange);
|
||||||
|
} else {
|
||||||
|
if (rangeSelectionHelper.areAllRangesRows(ranges, grid)) {
|
||||||
|
newRanges = rangeSelectionHelper.addRange(ranges, rowRange);
|
||||||
|
} else {
|
||||||
|
newRanges = [rowRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectionModel.setSelectedRanges(newRanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggleCheckbox = function (checkbox) {
|
||||||
|
if (checkbox.prop("checked")) {
|
||||||
|
checkbox.prop("checked", false)
|
||||||
|
} else {
|
||||||
|
checkbox.prop("checked", true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
|
||||||
|
columnDefinitions.unshift({
|
||||||
|
id: 'row-header-column',
|
||||||
|
name: '',
|
||||||
|
selectable: false,
|
||||||
|
focusable: false,
|
||||||
|
formatter: function (rowIndex) {
|
||||||
|
return '<input type="checkbox" ' +
|
||||||
|
'data-row="' + rowIndex + '" ' +
|
||||||
|
'data-cell-type="row-header-checkbox"/>'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return columnDefinitions;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.extend(this, {
|
||||||
|
"init": init,
|
||||||
|
"getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return RowSelector;
|
||||||
|
});
|
@ -96,7 +96,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group" role="group" aria-label="">
|
<div class="btn-group" role="group" aria-label="">
|
||||||
<button id="btn-copy-row" type="button" class="btn btn-default" title="{{ _('Copy Row') }}" disabled>
|
<button id="btn-copy-row" type="button" class="btn btn-default" title="{{ _('Copy') }}" disabled>
|
||||||
<i class="fa fa-files-o" aria-hidden="true"></i>
|
<i class="fa fa-files-o" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button id="btn-paste-row" type="button" class="btn btn-default" title="{{ _('Paste Row') }}" disabled>
|
<button id="btn-paste-row" type="button" class="btn btn-default" title="{{ _('Paste Row') }}" disabled>
|
||||||
|
@ -336,12 +336,6 @@ li {
|
|||||||
background-color: transparent; /* show default selected row background */
|
background-color: transparent; /* show default selected row background */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sc-cb {
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-right-color: silver;
|
|
||||||
border-right-style: solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#datagrid .slick-header .ui-state-default,
|
#datagrid .slick-header .ui-state-default,
|
||||||
#datagrid .slick-header .ui-widget-content.ui-state-default,
|
#datagrid .slick-header .ui-widget-content.ui-state-default,
|
||||||
#datagrid .slick-header .ui-widget-header .ui-state-default {
|
#datagrid .slick-header .ui-widget-header .ui-state-default {
|
||||||
@ -358,6 +352,10 @@ li {
|
|||||||
padding: 4px 0 4px 6px;
|
padding: 4px 0 4px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-description {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
.long_text_editor {
|
.long_text_editor {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
@ -419,6 +417,11 @@ input.editor-checkbox:focus {
|
|||||||
background: #e46b6b;
|
background: #e46b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* color the first column */
|
||||||
|
.sr .sc:first-child {
|
||||||
|
background-color: #2c76b4;
|
||||||
|
}
|
||||||
|
|
||||||
#datagrid div.slick-header.ui-state-default {
|
#datagrid div.slick-header.ui-state-default {
|
||||||
background: #2c76b4;
|
background: #2c76b4;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ define(
|
|||||||
[
|
[
|
||||||
'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin',
|
'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin',
|
||||||
'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
|
'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
|
||||||
'sources/selection/clipboard',
|
'sources/selection/grid_selector', 'sources/selection/clipboard',
|
||||||
|
'sources/selection/copy_data',
|
||||||
|
|
||||||
'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
|
'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
|
||||||
'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
|
'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
|
||||||
@ -21,13 +22,12 @@ define(
|
|||||||
'slickgrid/plugins/slick.cellrangedecorator',
|
'slickgrid/plugins/slick.cellrangedecorator',
|
||||||
'slickgrid/plugins/slick.cellrangeselector',
|
'slickgrid/plugins/slick.cellrangeselector',
|
||||||
'slickgrid/plugins/slick.cellselectionmodel',
|
'slickgrid/plugins/slick.cellselectionmodel',
|
||||||
'slickgrid/plugins/slick.checkboxselectcolumn',
|
|
||||||
'slickgrid/plugins/slick.cellcopymanager',
|
'slickgrid/plugins/slick.cellcopymanager',
|
||||||
'slickgrid/plugins/slick.rowselectionmodel',
|
'slickgrid/plugins/slick.rowselectionmodel',
|
||||||
'slickgrid/slick.grid'
|
'slickgrid/slick.grid'
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
$, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, clipboard
|
$, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData
|
||||||
) {
|
) {
|
||||||
/* Return back, this has been called more than once */
|
/* Return back, this has been called more than once */
|
||||||
if (pgAdmin.SqlEditor)
|
if (pgAdmin.SqlEditor)
|
||||||
@ -549,14 +549,7 @@ define(
|
|||||||
collection = [];
|
collection = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var grid_columns = new Array(),
|
var grid_columns = [];
|
||||||
checkboxSelector;
|
|
||||||
|
|
||||||
checkboxSelector = new Slick.CheckboxSelectColumn({
|
|
||||||
cssClass: "sc-cb"
|
|
||||||
});
|
|
||||||
|
|
||||||
grid_columns.push(checkboxSelector.getColumnDefinition());
|
|
||||||
|
|
||||||
var grid_width = $($('#editor-panel').find('.wcFrame')[1]).width()
|
var grid_width = $($('#editor-panel').find('.wcFrame')[1]).width()
|
||||||
_.each(columns, function(c) {
|
_.each(columns, function(c) {
|
||||||
@ -592,6 +585,9 @@ define(
|
|||||||
grid_columns.push(options)
|
grid_columns.push(options)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var gridSelector = new GridSelector();
|
||||||
|
grid_columns = gridSelector.getColumnDefinitionsWithCheckboxes(grid_columns);
|
||||||
|
|
||||||
var grid_options = {
|
var grid_options = {
|
||||||
editable: true,
|
editable: true,
|
||||||
enableAddRow: is_editable,
|
enableAddRow: is_editable,
|
||||||
@ -635,7 +631,7 @@ define(
|
|||||||
var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options);
|
var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options);
|
||||||
grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) );
|
grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) );
|
||||||
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
|
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
|
||||||
grid.registerPlugin(checkboxSelector);
|
grid.registerPlugin(gridSelector);
|
||||||
|
|
||||||
var editor_data = {
|
var editor_data = {
|
||||||
keys: self.handler.primary_keys,
|
keys: self.handler.primary_keys,
|
||||||
@ -2947,46 +2943,7 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
// This function will copy the selected row.
|
// This function will copy the selected row.
|
||||||
_copy_row: function() {
|
_copy_row: copyData,
|
||||||
var self = this, grid, data, rows, copied_text = '';
|
|
||||||
|
|
||||||
self.copied_rows = [];
|
|
||||||
|
|
||||||
// Disable copy button
|
|
||||||
$("#btn-copy-row").prop('disabled', true);
|
|
||||||
// Enable paste button
|
|
||||||
if(self.can_edit) {
|
|
||||||
$("#btn-paste-row").prop('disabled', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
grid = self.slickgrid;
|
|
||||||
data = grid.getData();
|
|
||||||
rows = grid.getSelectedRows();
|
|
||||||
// Iterate over all the selected rows & fetch data
|
|
||||||
for (var i = 0; i < rows.length; i += 1) {
|
|
||||||
var idx = rows[i],
|
|
||||||
_rowData = data[idx],
|
|
||||||
_values = [];
|
|
||||||
self.copied_rows.push(_rowData);
|
|
||||||
// Convert it as CSV for clipboard
|
|
||||||
for (var j = 0; j < self.columns.length; j += 1) {
|
|
||||||
var val = _rowData[self.columns[j].pos];
|
|
||||||
if(val && _.isObject(val))
|
|
||||||
val = "'" + JSON.stringify(val) + "'";
|
|
||||||
else if(val && typeof val != "number" && typeof true != "boolean")
|
|
||||||
val = "'" + val.toString() + "'";
|
|
||||||
else if (_.isNull(val) || _.isUndefined(val))
|
|
||||||
val = '';
|
|
||||||
_values.push(val);
|
|
||||||
}
|
|
||||||
// Append to main text string
|
|
||||||
if(_values.length > 0)
|
|
||||||
copied_text += _values.toString() + "\n";
|
|
||||||
}
|
|
||||||
// If there is something to set into clipboard
|
|
||||||
if(copied_text)
|
|
||||||
clipboard.copyTextToClipboard(copied_text);
|
|
||||||
},
|
|
||||||
|
|
||||||
// This function will paste the selected row.
|
// This function will paste the selected row.
|
||||||
_paste_row: function() {
|
_paste_row: function() {
|
||||||
|
@ -22,6 +22,13 @@ installed with:
|
|||||||
|
|
||||||
(pgadmin4) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
|
(pgadmin4) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
|
||||||
|
|
||||||
|
While running in Linux environments install:
|
||||||
|
|
||||||
|
(pgadmin4) $ sudo apt-get install xsel
|
||||||
|
|
||||||
|
Otherwise the following error happens:
|
||||||
|
"Pyperclip could not find a copy/paste mechanism for your system"
|
||||||
|
|
||||||
General Information
|
General Information
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ class BaseFeatureTest(BaseTestGenerator):
|
|||||||
|
|
||||||
self.page = PgadminPage(self.driver, app_config)
|
self.page = PgadminPage(self.driver, app_config)
|
||||||
try:
|
try:
|
||||||
|
self.page.driver.switch_to.default_content()
|
||||||
self.page.wait_for_app()
|
self.page.wait_for_app()
|
||||||
self.page.wait_for_spinner_to_disappear()
|
self.page.wait_for_spinner_to_disappear()
|
||||||
self.page.reset_layout()
|
self.page.reset_layout()
|
||||||
|
@ -58,7 +58,18 @@ class PgadminPage:
|
|||||||
|
|
||||||
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "']")
|
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "']")
|
||||||
|
|
||||||
|
def close_query_tool(self):
|
||||||
|
self.driver.switch_to.default_content()
|
||||||
|
tab = self.find_by_xpath("//*[contains(@class,'wcPanelTab') and contains(.,'" + "Query" + "')]")
|
||||||
|
ActionChains(self.driver).context_click(tab).perform()
|
||||||
|
self.find_by_xpath("//li[contains(@class, 'context-menu-item')]/span[contains(text(), 'Remove Panel')]").click()
|
||||||
|
self.driver.switch_to.frame(self.driver.find_elements_by_tag_name("iframe")[0])
|
||||||
|
time.sleep(.5)
|
||||||
|
self.click_element(self.find_by_xpath('//button[contains(@class, "ajs-button") and contains(.,"Yes")]'))
|
||||||
|
self.driver.switch_to.default_content()
|
||||||
|
|
||||||
def remove_server(self, server_config):
|
def remove_server(self, server_config):
|
||||||
|
self.driver.switch_to.default_content()
|
||||||
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
|
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
|
||||||
self.find_by_partial_link_text("Object").click()
|
self.find_by_partial_link_text("Object").click()
|
||||||
self.find_by_partial_link_text("Delete/Drop").click()
|
self.find_by_partial_link_text("Delete/Drop").click()
|
||||||
|
235
web/regression/javascript/selection/column_selector_spec.js
Normal file
235
web/regression/javascript/selection/column_selector_spec.js
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
define(
|
||||||
|
["jquery",
|
||||||
|
"underscore",
|
||||||
|
"slickgrid/slick.grid",
|
||||||
|
"sources/selection/column_selector",
|
||||||
|
"slickgrid/slick.rowselectionmodel",
|
||||||
|
"slickgrid"
|
||||||
|
],
|
||||||
|
function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) {
|
||||||
|
describe("ColumnSelector", function () {
|
||||||
|
var container, data, columns, options;
|
||||||
|
beforeEach(function () {
|
||||||
|
container = $("<div></div>");
|
||||||
|
container.height(9999);
|
||||||
|
|
||||||
|
data = [{'some-column-name': 'first value', 'second column': 'second value'}];
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'some-column-name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'second column',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'some-non-selectable-column',
|
||||||
|
selectable: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a column is not selectable", function () {
|
||||||
|
it("does not create a checkbox for selecting the column", function () {
|
||||||
|
var checkboxColumn = {
|
||||||
|
name: 'some-column-name-4',
|
||||||
|
selectable: false
|
||||||
|
};
|
||||||
|
columns.push(checkboxColumn);
|
||||||
|
|
||||||
|
setupGrid(columns);
|
||||||
|
|
||||||
|
expect(container.find('.slick-header-columns input').length).toBe(2)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a checkbox in the column header", function () {
|
||||||
|
setupGrid(columns);
|
||||||
|
|
||||||
|
expect(container.find('.slick-header-columns input').length).toBe(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
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('second column');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves the other attributes of column definitions", function () {
|
||||||
|
var columnSelector = new ColumnSelector();
|
||||||
|
var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
|
||||||
|
|
||||||
|
expect(selectableColumns[0].id).toBe('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("selecting columns", function () {
|
||||||
|
var grid, rowSelectionModel;
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
grid = new SlickGrid(container, data, columns, options);
|
||||||
|
|
||||||
|
rowSelectionModel = new RowSelectionModel();
|
||||||
|
grid.setSelectionModel(rowSelectionModel);
|
||||||
|
|
||||||
|
grid.registerPlugin(columnSelector);
|
||||||
|
grid.invalidate();
|
||||||
|
$("body").append(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
$("body").find(container).remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the user clicks additional column headers", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects additional columns", function () {
|
||||||
|
container.find('.slick-header-column')[0].click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
var column1 = selectedRanges[0];
|
||||||
|
|
||||||
|
expect(selectedRanges.length).toEqual(2);
|
||||||
|
expect(column1.fromCell).toBe(1);
|
||||||
|
expect(column1.toCell).toBe(1);
|
||||||
|
|
||||||
|
var column2 = selectedRanges[1];
|
||||||
|
|
||||||
|
expect(column2.fromCell).toBe(0);
|
||||||
|
expect(column2.toCell).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the user clicks a column header checkbox", function () {
|
||||||
|
it("selects the column", function () {
|
||||||
|
container.find('.slick-header-columns input')[0].click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a row is selected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
|
||||||
|
rowSelectionModel.setSelectedRanges(selectedRanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deselects the row", function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("clicking a second time", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unchecks checkbox", function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deselects the column", function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
|
||||||
|
expect(selectedRanges.length).toEqual(0);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the column is deselected through setSelectedRanges", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find('.slick-header-column')[1].click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unchecks the checkbox", function () {
|
||||||
|
rowSelectionModel.setSelectedRanges([]);
|
||||||
|
|
||||||
|
expect($(container.find('.slick-header-columns input')[1])
|
||||||
|
.is(':checked')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a non-column range was already selected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
var selectedRanges = [new Slick.Range(0, 0, 1, 0)];
|
||||||
|
rowSelectionModel.setSelectedRanges(selectedRanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deselects the non-column range", function () {
|
||||||
|
container.find('.slick-header-column')[0].click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var setupGrid = function (columns) {
|
||||||
|
var columnSelector = new ColumnSelector();
|
||||||
|
columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
|
||||||
|
var grid = new SlickGrid(container, data, columns, options);
|
||||||
|
|
||||||
|
var rowSelectionModel = new RowSelectionModel();
|
||||||
|
grid.setSelectionModel(rowSelectionModel);
|
||||||
|
|
||||||
|
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.fromRow).toBe(0);
|
||||||
|
expect(row.toRow).toBe(9);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
119
web/regression/javascript/selection/copy_data_spec.js
Normal file
119
web/regression/javascript/selection/copy_data_spec.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
define(
|
||||||
|
["jquery",
|
||||||
|
"slickgrid/slick.grid",
|
||||||
|
"slickgrid/slick.rowselectionmodel",
|
||||||
|
"sources/selection/copy_data",
|
||||||
|
"sources/selection/clipboard",
|
||||||
|
"sources/selection/range_selection_helper"
|
||||||
|
],
|
||||||
|
function ($, SlickGrid, RowSelectionModel, copyData, clipboard, RangeSelectionHelper) {
|
||||||
|
describe('copyData', function () {
|
||||||
|
var grid, sqlEditor;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
var data = [[1, "leopord", "12"],
|
||||||
|
[2, "lion", "13"],
|
||||||
|
[3, "puma", "9"]];
|
||||||
|
|
||||||
|
var columns = [{
|
||||||
|
name: "id",
|
||||||
|
pos: 0,
|
||||||
|
label: "id<br> numeric",
|
||||||
|
cell: "number",
|
||||||
|
can_edit: false,
|
||||||
|
type: "numeric"
|
||||||
|
}, {
|
||||||
|
name: "brand",
|
||||||
|
pos: 1,
|
||||||
|
label: "flavor<br> character varying",
|
||||||
|
cell: "string",
|
||||||
|
can_edit: false,
|
||||||
|
type: "character varying"
|
||||||
|
}, {
|
||||||
|
name: "size",
|
||||||
|
pos: 2,
|
||||||
|
label: "size<br> numeric",
|
||||||
|
cell: "number",
|
||||||
|
can_edit: false,
|
||||||
|
type: "numeric"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
;
|
||||||
|
var gridContainer = $("<div id='grid'></div>");
|
||||||
|
$("body").append(gridContainer);
|
||||||
|
$("body").append("<button id='btn-paste-row' disabled></button>");
|
||||||
|
grid = new Slick.Grid("#grid", data, columns, {});
|
||||||
|
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
|
||||||
|
sqlEditor = {slickgrid: grid};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
$("body").remove('#grid');
|
||||||
|
$("body").remove('#btn-paste-row');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when rows are selected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
grid.getSelectionModel().setSelectedRanges([
|
||||||
|
RangeSelectionHelper.rangeForRow(grid, 0),
|
||||||
|
RangeSelectionHelper.rangeForRow(grid, 2)]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copies them", function () {
|
||||||
|
spyOn(clipboard, 'copyTextToClipboard');
|
||||||
|
|
||||||
|
copyData.apply(sqlEditor);
|
||||||
|
|
||||||
|
expect(sqlEditor.copied_rows.length).toBe(2);
|
||||||
|
|
||||||
|
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
|
||||||
|
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("1,'leopord','12'");
|
||||||
|
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain("3,'puma','9'");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the user can edit the grid", function () {
|
||||||
|
it("enables the paste row button", function () {
|
||||||
|
copyData.apply(_.extend({can_edit: true}, sqlEditor));
|
||||||
|
|
||||||
|
expect($("#btn-paste-row").prop('disabled')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a column is selected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
var firstColumn = new Slick.Range(0, 0, 2, 0);
|
||||||
|
grid.getSelectionModel().setSelectedRanges([firstColumn])
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copies text to the clipboard", function () {
|
||||||
|
spyOn(clipboard, 'copyTextToClipboard');
|
||||||
|
|
||||||
|
copyData.apply(sqlEditor);
|
||||||
|
|
||||||
|
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
|
||||||
|
|
||||||
|
var copyArg = clipboard.copyTextToClipboard.calls.mostRecent().args[0];
|
||||||
|
var rowStrings = copyArg.split('\n');
|
||||||
|
expect(rowStrings[0]).toBe("1");
|
||||||
|
expect(rowStrings[1]).toBe("2");
|
||||||
|
expect(rowStrings[2]).toBe("3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets copied_rows to empty", function () {
|
||||||
|
copyData.apply(sqlEditor);
|
||||||
|
|
||||||
|
expect(sqlEditor.copied_rows.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the user can edit the grid", function () {
|
||||||
|
it("disables the paste row button", function () {
|
||||||
|
copyData.apply(_.extend({can_edit: true}, sqlEditor));
|
||||||
|
|
||||||
|
expect($("#btn-paste-row").prop('disabled')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
126
web/regression/javascript/selection/grid_selector_spec.js
Normal file
126
web/regression/javascript/selection/grid_selector_spec.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
define(["jquery",
|
||||||
|
"underscore",
|
||||||
|
"slickgrid/slick.grid",
|
||||||
|
"slickgrid/slick.rowselectionmodel",
|
||||||
|
"sources/selection/grid_selector"
|
||||||
|
],
|
||||||
|
function ($, _, SlickGrid, RowSelectionModel, GridSelector) {
|
||||||
|
describe("GridSelector", function () {
|
||||||
|
var container, data, columns, gridSelector, rowSelectionModel;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
container = $("<div></div>");
|
||||||
|
container.height(9999);
|
||||||
|
columns = [{
|
||||||
|
id: '1',
|
||||||
|
name: 'some-column-name',
|
||||||
|
}, {
|
||||||
|
id: '2',
|
||||||
|
name: 'second column',
|
||||||
|
}];
|
||||||
|
|
||||||
|
gridSelector = new GridSelector();
|
||||||
|
columns = gridSelector.getColumnDefinitionsWithCheckboxes(columns);
|
||||||
|
|
||||||
|
data = [];
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
|
||||||
|
}
|
||||||
|
var grid = new SlickGrid(container, data, columns);
|
||||||
|
|
||||||
|
rowSelectionModel = new RowSelectionModel();
|
||||||
|
grid.setSelectionModel(rowSelectionModel);
|
||||||
|
|
||||||
|
grid.registerPlugin(gridSelector);
|
||||||
|
grid.invalidate();
|
||||||
|
|
||||||
|
$("body").append(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
$("body").find(container).remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders an additional column on the left for selecting rows", function () {
|
||||||
|
expect(columns.length).toBe(3);
|
||||||
|
|
||||||
|
var leftmostColumn = columns[0];
|
||||||
|
expect(leftmostColumn.id).toBe('row-header-column');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders checkboxes for selecting columns", function () {
|
||||||
|
expect(container.find('[data-test="output-column-header"] input').length).toBe(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a checkbox for selecting all the cells", function () {
|
||||||
|
expect(container.find("[title='Select/Deselect All']").length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the cell for the select/deselect all is clicked", function () {
|
||||||
|
it("selects the whole grid", function () {
|
||||||
|
container.find("[title='Select/Deselect All']").parent().click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expect(selectedRanges.length).toBe(1);
|
||||||
|
var selectedRange = selectedRanges[0];
|
||||||
|
expect(selectedRange.fromCell).toBe(1);
|
||||||
|
expect(selectedRange.toCell).toBe(2);
|
||||||
|
expect(selectedRange.fromRow).toBe(0);
|
||||||
|
expect(selectedRange.toRow).toBe(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks the checkbox", function () {
|
||||||
|
container.find("[title='Select/Deselect All']").parent().click();
|
||||||
|
|
||||||
|
expect($(container.find("[data-id='checkbox-select-all']")).is(':checked')).toBeTruthy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the main checkbox in the corner gets selected", function () {
|
||||||
|
it("unchecks all the columns", function () {
|
||||||
|
container.find("[title='Select/Deselect All']").click();
|
||||||
|
|
||||||
|
expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
|
||||||
|
expect($(container.find('.slick-header-columns input')[2]).is(':checked')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects all the cells", function () {
|
||||||
|
container.find("[title='Select/Deselect All']").click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expect(selectedRanges.length).toBe(1);
|
||||||
|
var selectedRange = selectedRanges[0];
|
||||||
|
expect(selectedRange.fromCell).toBe(1);
|
||||||
|
expect(selectedRange.toCell).toBe(2);
|
||||||
|
expect(selectedRange.fromRow).toBe(0);
|
||||||
|
expect(selectedRange.toRow).toBe(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the main checkbox in the corner gets deselected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find("[title='Select/Deselect All']").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deselects all the cells", function () {
|
||||||
|
container.find("[title='Select/Deselect All']").click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expect(selectedRanges.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and then the underlying selection changes", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find("[title='Select/Deselect All']").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unchecks the main checkbox", function () {
|
||||||
|
var ranges = [new Slick.Range(0, 0, 0, 1)];
|
||||||
|
rowSelectionModel.setSelectedRanges(ranges);
|
||||||
|
|
||||||
|
expect($(container.find("[title='Select/Deselect All']")).is(':checked')).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,158 @@
|
|||||||
|
define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNavigator) {
|
||||||
|
|
||||||
|
describe("#getUnion", function () {
|
||||||
|
describe("when the ranges completely overlap", function () {
|
||||||
|
it("returns a list with that range", function () {
|
||||||
|
var ranges = [[1, 4], [1, 4], [1, 4]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(ranges);
|
||||||
|
|
||||||
|
expect(union).toEqual([[1, 4]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the ranges all overlap partially or touch", function () {
|
||||||
|
it("returns one long range", function () {
|
||||||
|
var rangeBounds = [[3, 6], [1, 4], [7, 14]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
|
||||||
|
|
||||||
|
expect(union).toEqual([[1, 14]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns them in order from lowest to highest", function () {
|
||||||
|
var rangeBounds = [[3, 6], [2, 3], [10, 12]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
|
||||||
|
|
||||||
|
expect(union).toEqual([[2, 6], [10, 12]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when one range completely overlaps another", function() {
|
||||||
|
|
||||||
|
it("returns them in order from lowest to highest", function () {
|
||||||
|
var rangeBounds = [[9, 14], [2, 3], [11, 13]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
|
||||||
|
|
||||||
|
expect(union).toEqual([[2, 3], [9, 14]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when one range is a subset of another", function () {
|
||||||
|
it("returns the larger range", function () {
|
||||||
|
var rangeBounds = [[2, 6], [1, 14], [8, 10]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
|
||||||
|
|
||||||
|
expect(union).toEqual([[1, 14]]);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the ranges do not touch", function () {
|
||||||
|
it("returns them in order from lowest to highest", function () {
|
||||||
|
var rangeBounds = [[3, 6], [1, 1], [8, 10]];
|
||||||
|
|
||||||
|
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
|
||||||
|
|
||||||
|
expect(union).toEqual([[1, 1], [3, 6], [8, 10]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("#mapDimensionBoundaryUnion", function () {
|
||||||
|
it("returns a list of the results of the callback", function () {
|
||||||
|
var rangeBounds = [[0, 1], [3, 3]];
|
||||||
|
var callback = function () {
|
||||||
|
return 'hello';
|
||||||
|
};
|
||||||
|
var result = rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
|
||||||
|
expect(result).toEqual(['hello', 'hello', 'hello']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls the callback with each index in the dimension", function () {
|
||||||
|
var rangeBounds = [[0, 1], [3, 3]];
|
||||||
|
var callback = jasmine.createSpy('callbackSpy');
|
||||||
|
rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
|
||||||
|
expect(callback.calls.allArgs()).toEqual([[0], [1], [3]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#mapOver2DArray", function () {
|
||||||
|
var data, rowCollector, processCell;
|
||||||
|
beforeEach(function () {
|
||||||
|
data = [[0, 1, 2, 3], [2, 2, 2, 2], [4, 5, 6, 7]];
|
||||||
|
processCell = function (rowIndex, columnIndex) {
|
||||||
|
return data[rowIndex][columnIndex];
|
||||||
|
};
|
||||||
|
rowCollector = function (rowData) {
|
||||||
|
return JSON.stringify(rowData);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls the callback for each item in the ranges", function () {
|
||||||
|
var rowRanges = [[0, 0], [2, 2]];
|
||||||
|
var colRanges = [[0, 3]];
|
||||||
|
|
||||||
|
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
|
||||||
|
|
||||||
|
expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the ranges are out of order/duplicated", function () {
|
||||||
|
var rowRanges, colRanges;
|
||||||
|
beforeEach(function () {
|
||||||
|
rowRanges = [[2, 2], [2, 2], [0, 0]];
|
||||||
|
colRanges = [[0, 3]];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the union of the ranges", function () {
|
||||||
|
spyOn(rangeBoundaryNavigator, "getUnion").and.callThrough();
|
||||||
|
|
||||||
|
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
|
||||||
|
|
||||||
|
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(rowRanges);
|
||||||
|
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(colRanges);
|
||||||
|
expect(selectionResult).toEqual(["[0,1,2,3]", "[4,5,6,7]"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#rangesToCsv", function () {
|
||||||
|
var data, columnDefinitions, ranges;
|
||||||
|
beforeEach(function () {
|
||||||
|
data = [[1, "leopard", "12"],
|
||||||
|
[2, "lion", "13"],
|
||||||
|
[3, "cougar", "9"],
|
||||||
|
[4, "tiger", "10"]];
|
||||||
|
columnDefinitions = [{name: 'id', pos: 0}, {name: 'animal', pos: 1}, {name: 'size', pos: 2}];
|
||||||
|
ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)];
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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 () {
|
||||||
|
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
|
||||||
|
|
||||||
|
expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
174
web/regression/javascript/selection/row_selector_spec.js
Normal file
174
web/regression/javascript/selection/row_selector_spec.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
define(
|
||||||
|
["jquery",
|
||||||
|
"underscore",
|
||||||
|
"slickgrid/slick.grid",
|
||||||
|
"sources/selection/row_selector",
|
||||||
|
"slickgrid/slick.rowselectionmodel",
|
||||||
|
"slickgrid",
|
||||||
|
],
|
||||||
|
function ($, _, SlickGrid, RowSelector, RowSelectionModel, Slick) {
|
||||||
|
describe("RowSelector", function () {
|
||||||
|
var container, data, columnDefinitions, grid, rowSelectionModel;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
container = $("<div></div>");
|
||||||
|
container.height(9999);
|
||||||
|
|
||||||
|
columnDefinitions = [{
|
||||||
|
id: '1',
|
||||||
|
name: 'some-column-name',
|
||||||
|
selectable: true
|
||||||
|
}, {
|
||||||
|
id: '2',
|
||||||
|
name: 'second column',
|
||||||
|
selectable: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
var rowSelector = new RowSelector();
|
||||||
|
data = [];
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
data.push(['some-value-' + i, 'second value ' + i]);
|
||||||
|
}
|
||||||
|
columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
|
||||||
|
grid = new SlickGrid(container, data, columnDefinitions);
|
||||||
|
|
||||||
|
rowSelectionModel = new RowSelectionModel();
|
||||||
|
grid.setSelectionModel(rowSelectionModel);
|
||||||
|
grid.registerPlugin(rowSelector);
|
||||||
|
grid.invalidate();
|
||||||
|
|
||||||
|
$("body").append(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
$("body").find(container).remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders an additional column on the left", function () {
|
||||||
|
expect(columnDefinitions.length).toBe(3);
|
||||||
|
|
||||||
|
var leftmostColumn = columnDefinitions[0];
|
||||||
|
expect(leftmostColumn.id).toBe('row-header-column');
|
||||||
|
expect(leftmostColumn.name).toBe('');
|
||||||
|
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("preserves the other attributes of column definitions", function () {
|
||||||
|
expect(columnDefinitions[1].id).toBe('1');
|
||||||
|
expect(columnDefinitions[1].selectable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("selecting rows", function () {
|
||||||
|
describe("when the user clicks a row header checkbox", function () {
|
||||||
|
it("selects the row", function () {
|
||||||
|
container.find('.sr .sc:first-child input[type="checkbox"]')[0].click();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expectOnlyTheFirstRowToBeSelected(selectedRanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks the checkbox", function () {
|
||||||
|
container.find('.sr .sc:first-child input[type="checkbox"]')[5].click();
|
||||||
|
|
||||||
|
expect($(container.find('.sr .sc: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();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expectOnlyTheFirstRowToBeSelected(selectedRanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks the checkbox", function () {
|
||||||
|
container.find('.sr .sc:first-child')[7].click();
|
||||||
|
|
||||||
|
expect($(container.find('.sr .sc: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();
|
||||||
|
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
expect(selectedRanges.length).toEqual(2);
|
||||||
|
|
||||||
|
var row1 = selectedRanges[0];
|
||||||
|
expect(row1.fromRow).toBe(4);
|
||||||
|
expect(row1.toRow).toBe(4);
|
||||||
|
|
||||||
|
var row2 = selectedRanges[1];
|
||||||
|
expect(row2.fromRow).toBe(0);
|
||||||
|
expect(row2.toRow).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a column was already selected", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
|
||||||
|
rowSelectionModel.setSelectedRanges(selectedRanges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deselects the column", function () {
|
||||||
|
container.find('.sr .sc:first-child')[0].click();
|
||||||
|
var selectedRanges = rowSelectionModel.getSelectedRanges();
|
||||||
|
|
||||||
|
expectOnlyTheFirstRowToBeSelected(selectedRanges);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the row is deselected through setSelectedRanges", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
container.find('.sr .sc:first-child')[4].click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should uncheck the checkbox", function () {
|
||||||
|
rowSelectionModel.setSelectedRanges([]);
|
||||||
|
|
||||||
|
expect($(container.find('.sr .sc: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();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unchecks checkbox", function () {
|
||||||
|
container.find('.sr .sc:first-child')[1].click();
|
||||||
|
expect($(container.find('.sr .sc: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();
|
||||||
|
|
||||||
|
expect(selectedRanges.length).toEqual(0);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectOnlyTheFirstRowToBeSelected(selectedRanges) {
|
||||||
|
var row = selectedRanges[0];
|
||||||
|
|
||||||
|
expect(selectedRanges.length).toEqual(1);
|
||||||
|
expect(row.fromCell).toBe(1);
|
||||||
|
expect(row.toCell).toBe(2);
|
||||||
|
expect(row.fromRow).toBe(0);
|
||||||
|
expect(row.toRow).toBe(0);
|
||||||
|
}
|
||||||
|
});
|
@ -163,6 +163,9 @@ def create_table(server, db_name, table_name):
|
|||||||
table_name)
|
table_name)
|
||||||
pg_cursor.execute(
|
pg_cursor.execute(
|
||||||
'''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name)
|
'''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name)
|
||||||
|
pg_cursor.execute(
|
||||||
|
'''INSERT INTO "%s" VALUES ('Some-Other-Name', 22)''' % table_name)
|
||||||
|
|
||||||
connection.set_isolation_level(old_isolation_level)
|
connection.set_isolation_level(old_isolation_level)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
chromedriver_installer==0.0.6
|
chromedriver_installer==0.0.6
|
||||||
|
pyperclip~=1.5.27
|
||||||
selenium==3.3.1
|
selenium==3.3.1
|
||||||
testscenarios==0.5.0
|
testscenarios==0.5.0
|
||||||
testtools==2.0.0
|
testtools==2.0.0
|
||||||
|
Loading…
Reference in New Issue
Block a user