Extract row staging into it's own module with tests.

This commit is contained in:
George Gelashvili 2017-06-07 14:17:10 +01:00 committed by Dave Page
parent 0d05385585
commit 8d8e7dab3c
3 changed files with 356 additions and 83 deletions

View File

@ -0,0 +1,114 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define(
[
'jquery',
'underscore'
],
function ($, _) {
function disableButton(selector) {
$(selector).prop('disabled', true);
}
function enableButton(selector) {
$(selector).prop('disabled', false);
}
function getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData) {
return _.reduce(selectedRows, function (primaryKeyValuesToStage, dataGridRowIndex) {
var gridRow = gridData[dataGridRowIndex];
if (isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices)) {
return primaryKeyValuesToStage;
}
var tempPK = gridRow.__temp_PK;
primaryKeyValuesToStage[tempPK] = getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow);
return primaryKeyValuesToStage;
}, {});
}
function isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices) {
if (_.isUndefined(gridRow)) {
return true;
}
return !_.isUndefined(
_.find(primaryKeyColumnIndices, function (pkIndex) {
return _.isUndefined(gridRow[pkIndex]);
})
);
}
function getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow) {
var rowToStage = {};
if (primaryKeyColumnIndices.length) {
_.each(_.keys(gridRow), function (columnPos) {
if (_.contains(primaryKeyColumnIndices, Number(columnPos)))
rowToStage[columnPos] = gridRow[columnPos];
})
}
return rowToStage;
}
function getPrimaryKeysForSelectedRows(self, selectedRows) {
var primaryKeyColumnIndices = _.map(_.keys(self.keys), function (columnName) {
var columnInfo = _.findWhere(self.columns, {name: columnName});
return columnInfo['pos'];
});
var gridData = self.grid.getData();
var stagedRows = getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData);
return stagedRows;
}
var setStagedRows = function (e, args) {
var self = this;
function setStagedRows(rowsToStage) {
self.editor.handler.data_store.staged_rows = rowsToStage;
}
function isEditMode() {
return self.editor.handler.can_edit;
}
disableButton('#btn-delete-row');
disableButton('#btn-copy-row');
if (!_.has(this.selection, 'getSelectedRows')) {
setStagedRows({});
return;
}
var selectedRows = this.selection.getSelectedRows();
if (selectedRows.length > 0) {
var stagedRows = getPrimaryKeysForSelectedRows(self, selectedRows);
setStagedRows(stagedRows);
if (_.isEmpty(stagedRows)) {
this.selection.setSelectedRows([]);
}
enableButton('#btn-copy-row');
if (isEditMode()) {
enableButton('#btn-delete-row');
}
} else {
setStagedRows({});
}
};
return setStagedRows;
}
);

View File

@ -3,6 +3,7 @@ define([
'pgadmin', 'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
'sources/selection/grid_selector', 'sources/selection/clipboard',
'sources/selection/copy_data',
'sources/selection/set_staged_rows',
'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
'codemirror/addon/selection/active-line', 'codemirror/addon/fold/foldcode',
@ -25,7 +26,7 @@ define([
'slickgrid/slick.grid'
], function(
gettext, $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror,
pgExplain, GridSelector, clipboard, copyData
pgExplain, GridSelector, clipboard, copyData, setStagedRows
) {
/* Return back, this has been called more than once */
if (pgAdmin.SqlEditor)
@ -691,88 +692,8 @@ define([
// Listener function to watch selected rows from grid
if (editor_data.selection) {
editor_data.selection.onSelectedRangesChanged.subscribe(function(e, args) {
var collection = this.grid.getData(),
primary_key_list = _.keys(this.keys),
_tmp_keys = [],
_columns = this.columns,
rows_for_stage = {},
selected_rows_list = [];
// Only if entire row(s) are selected via check box
if(_.has(this.selection, 'getSelectedRows')) {
selected_rows_list = this.selection.getSelectedRows();
// We will map selected row primary key name with position
// For each Primary key
_.each(primary_key_list, function(p) {
// For each columns search primary key position
_.each(_columns, function(c) {
if(c.name == p) {
_tmp_keys.push(c.pos);
}
});
});
// Now assign mapped temp PK to PK
primary_key_list = _tmp_keys;
// Check if selected is new row ?
// Allow to delete if yes
var count = selected_rows_list.length-1,
cell_el = this.grid.getCellNode(selected_rows_list[count],0),
parent_el = $(cell_el).parent(),
is_new_row = $(parent_el).hasClass('new_row');
// Clear selection model if row primary keys is set to default
var row_data = _.clone(collection[selected_rows_list[count]]),
is_primary_key = true;
// Primary key validation
_.each(primary_key_list, function(pk) {
if (!(_.has(row_data, pk)) || row_data[pk] == undefined) {
is_primary_key = false;
}
})
if (primary_key_list.length && !is_primary_key && !is_new_row) {
this.selection.setSelectedRows([]);
selected_rows_list = [];
}
}
// Clear the object as no rows to delete
// and disable delete/copy rows button
var clear_staged_rows = function() {
rows_for_stage = {};
$("#btn-delete-row").prop('disabled', true);
$("#btn-copy-row").prop('disabled', true);
}
// If any row(s) selected ?
if(selected_rows_list.length) {
if(this.editor.handler.can_edit)
// Enable delete rows and copy rows button
$("#btn-delete-row").prop('disabled', false);
$("#btn-copy-row").prop('disabled', false);
// Collect primary key data from collection as needed for stage row
_.each(selected_rows_list, function(row_index) {
var row_data = collection[row_index],
p_keys_list = _.pick(row_data, primary_key_list),
is_primary_key = Object.keys(p_keys_list).length ?
p_keys_list[0] : undefined;
// Store Primary key data for selected rows
if (!_.isUndefined(row_data) && !_.isUndefined(p_keys_list)) {
// check for invalid row
rows_for_stage[row_data.__temp_PK] = p_keys_list;
}
});
} else {
//clear staged rows
clear_staged_rows();
}
// Update main data store
this.editor.handler.data_store.staged_rows = rows_for_stage;
}.bind(editor_data));
editor_data.selection.onSelectedRangesChanged.subscribe(
setStagedRows.bind(editor_data));
}

View File

@ -0,0 +1,238 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
"jquery",
"underscore",
"sources/selection/set_staged_rows",
], function ($, _, SetStagedRows) {
describe('when no full rows are selected', function () {
var sqlEditorObj, deleteButton, copyButton;
beforeEach(function () {
var gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']);
gridSpy.getData.and.returnValue([
{0: 'one', 1: 'two', __temp_PK: '123'},
{0: 'three', 1: 'four', __temp_PK: '456'},
{0: 'five', 1: 'six', __temp_PK: '789'},
{0: 'seven', 1: 'eight', __temp_PK: '432'}
]);
deleteButton = $('<button id="btn-delete-row"></button>');
copyButton = $('<button id="btn-copy-row"></button>');
sqlEditorObj = {
grid: gridSpy,
editor: {
handler: {
data_store: {
staged_rows: {1: [1, 2]}
}
}
}
};
$('body').append(deleteButton);
$('body').append(copyButton);
deleteButton.prop('disabled', false);
copyButton.prop('disabled', false);
});
afterEach(function () {
copyButton.remove();
deleteButton.remove();
});
describe('when getSelectedRows is not present in the selection model', function () {
beforeEach(function () {
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
describe('when getSelectedRows is present in the selection model', function () {
beforeEach(function () {
var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']);
selectionSpy.getSelectedRows.and.returnValue([]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
});
describe('when 2 full rows are selected', function () {
describe('when getSelectedRows is present in the selection model', function () {
var sqlEditorObj, gridSpy, deleteButton, copyButton;
beforeEach(function () {
gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']);
gridSpy.getData.and.returnValue([
{0: 'one', 1: 'two', __temp_PK: '123'},
{0: 'three', 1: 'four', __temp_PK: '456'},
{0: 'five', 1: 'six', __temp_PK: '789'},
{0: 'seven', 1: 'eight', __temp_PK: '432'}
]);
var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']);
selectionSpy.getSelectedRows.and.returnValue([1, 2]);
deleteButton = $('<button id="btn-delete-row"></button>');
copyButton = $('<button id="btn-copy-row"></button>');
sqlEditorObj = {
grid: gridSpy,
editor: {
handler: {
data_store: {
staged_rows: {'456': {}}
},
can_edit: false
}
},
keys: null,
selection: selectionSpy,
columns: [
{
name: 'a pk column',
pos: 0
},
{
name: 'some column',
pos: 1
}
]
};
$('body').append(deleteButton);
$('body').append(copyButton);
deleteButton.prop('disabled', true);
copyButton.prop('disabled', true);
});
afterEach(function () {
copyButton.remove();
deleteButton.remove();
});
describe('when table does not have primary keys', function () {
it('should enable the copy row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should not enable the delete row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
});
describe('the user can edit', function () {
it('should enable the delete row button', function () {
sqlEditorObj.editor.handler.can_edit = true;
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
});
});
});
describe('when table has primary keys', function () {
beforeEach(function () {
sqlEditorObj.keys = {'a pk column': 'varchar'};
sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {0: 'three'}};
});
describe('selected rows have primary key', function () {
it('should set the staged rows correctly', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
{'456': {0: 'three'}, '789': {0: 'five'}});
});
it('should not clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
});
});
describe('selected rows missing primary key', function () {
beforeEach(function () {
gridSpy.getData.and.returnValue([
{0: 'one', 1: 'two', __temp_PK: '123'},
{1: 'four', __temp_PK: '456'},
{1: 'six', __temp_PK: '789'},
{0: 'seven', 1: 'eight', __temp_PK: '432'}
]);
});
it('should clear the staged rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
it('should clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
});
});
describe('when the selected row is a new row', function () {
var parentDiv;
beforeEach(function () {
var childDiv = $('<div></div>');
parentDiv = $('<div class="new_row"></div>');
parentDiv.append(childDiv);
$('body').append(parentDiv);
gridSpy.getCellNode.and.returnValue(childDiv);
SetStagedRows.call(sqlEditorObj, {}, {});
});
afterEach(function () {
parentDiv.remove();
});
it('should not clear the staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
'456': {0: 'three'},
'789': {0: 'five'}
});
});
it('should not clear selected rows in Cell Selection Model', function () {
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
});
});
});
});
});
});