Allow column or row selection in the query tool. Fixes #2216

This commit is contained in:
Matthew Kleiman
2017-04-18 13:28:45 +01:00
committed by Dave Page
parent c35dbc7211
commit d663d553c5
21 changed files with 1427 additions and 59 deletions

View 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);
}
});
});

View 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);
});
});
});
});
});

View 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();
});
});
});
});
});

View File

@@ -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'");
});
});
});
});

View 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);
}
});