mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Add infrastructure for managing configurable keyboard shortcuts.
This commit is contained in:
parent
7c985695b7
commit
66341e6947
@ -25,10 +25,6 @@ div.pgadmin-preference-body div.ajs-content {
|
||||
bottom: 0px !important;
|
||||
}
|
||||
|
||||
.preferences_content .control-label, .preferences_content .pgadmin-controls {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.pgadmin-preference-body {
|
||||
min-width: 300px !important;
|
||||
min-height: 400px !important;
|
||||
@ -40,3 +36,15 @@ div.pgadmin-preference-body div.ajs-content {
|
||||
min-height: 480px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.keyboard-shortcut-label {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.preferences_content input[type="checkbox"]:focus {
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.preferences_content .pgadmin-controls input[type="checkbox"] {
|
||||
margin-left: -20px !important;
|
||||
}
|
||||
|
@ -256,6 +256,8 @@ define('pgadmin.preferences', [
|
||||
return 'textarea';
|
||||
case 'switch':
|
||||
return 'switch';
|
||||
case 'keyboardshortcut':
|
||||
return 'keyboardShortcut';
|
||||
default:
|
||||
if (console && console.warn) {
|
||||
// Warning for developer only.
|
||||
|
@ -2504,5 +2504,206 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
var KeyCodeControlFormatter = Backform.KeyCodeControlFormatter = function() {};
|
||||
|
||||
_.extend(KeyCodeControlFormatter.prototype, {
|
||||
fromRaw: function (rawData) {
|
||||
return rawData['char'];
|
||||
},
|
||||
// we don't need toRaw
|
||||
toRaw: undefined,
|
||||
});
|
||||
|
||||
Backform.KeyCodeControl = Backform.InputControl.extend({
|
||||
defaults: _.defaults({
|
||||
escapeKeyCodes: [16, 17, 18, 27], // Shift, Ctrl, Alt/Option, Escape
|
||||
}, Backform.InputControl.prototype.defaults),
|
||||
|
||||
events: {
|
||||
'keydown input': 'onkeyDown',
|
||||
'keyup input': 'preventEvent',
|
||||
'focus select': 'clearInvalid',
|
||||
},
|
||||
|
||||
formatter: KeyCodeControlFormatter,
|
||||
|
||||
preventEvent: function(e) {
|
||||
var key_code = e.which || e.keyCode,
|
||||
field = _.defaults(this.field.toJSON(), this.defaults);
|
||||
|
||||
if (field.escapeKeyCodes.indexOf(key_code) != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%> keyboard-shortcut-label"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" oncopy="return false; oncut="return false; onpaste="return false;" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
||||
' <% if (helpMessage && helpMessage.length) { %>',
|
||||
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
||||
' <% } %>',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
onkeyDown: function(e) {
|
||||
var self = this,
|
||||
model = this.model,
|
||||
attrArr = this.field.get('name').split('.'),
|
||||
name = attrArr.shift(),
|
||||
changes = {},
|
||||
key_code = e.which || e.keyCode,
|
||||
key,
|
||||
field = _.defaults(this.field.toJSON(), this.defaults);
|
||||
|
||||
if (field.escapeKeyCodes.indexOf(key_code) != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.model.errorModel instanceof Backbone.Model) {
|
||||
this.model.errorModel.unset(name);
|
||||
}
|
||||
key = gettext(e.key);
|
||||
if (key_code == 32) {
|
||||
key = gettext('Space');
|
||||
}
|
||||
changes = {
|
||||
'key_code': e.which || e.keyCode,
|
||||
'char': key,
|
||||
};
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
model.set(name, changes);
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
setTimeout(function() {
|
||||
self.$el.find('input').val(key);
|
||||
});
|
||||
e.preventDefault();
|
||||
},
|
||||
keyPathAccessor: function(obj, path) {
|
||||
var res = obj;
|
||||
path = path.split('.');
|
||||
for (var i = 0; i < path.length; i++) {
|
||||
if (_.isNull(res)) return null;
|
||||
if (_.isEmpty(path[i])) continue;
|
||||
if (!_.isUndefined(res[path[i]])) res = res[path[i]];
|
||||
}
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
Backform.KeyboardShortcutControl = Backform.Control.extend({
|
||||
|
||||
initialize: function() {
|
||||
|
||||
Backform.Control.prototype.initialize.apply(this, arguments);
|
||||
|
||||
var fields = this.field.get('fields');
|
||||
|
||||
if (fields == null || fields == undefined) {
|
||||
throw new ReferenceError('"fields" not found in keyboard shortcut');
|
||||
}
|
||||
|
||||
this.innerModel = new Backbone.Model();
|
||||
|
||||
this.controls = [];
|
||||
},
|
||||
cleanup: function() {
|
||||
|
||||
this.stopListening(this.innerModel, 'change', this.onInnerModelChange);
|
||||
|
||||
_.each(this.controls, function(c) {
|
||||
c.remove();
|
||||
});
|
||||
|
||||
this.controls.length = 0;
|
||||
},
|
||||
template: _.template([
|
||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||
'<div class="<%=Backform.controlsClassName%>">',
|
||||
'</div>',
|
||||
].join('\n')),
|
||||
|
||||
onInnerModelChange: function() {
|
||||
|
||||
var name = this.field.get('name'),
|
||||
val = $.extend(true, {}, this.model.get(name));
|
||||
|
||||
this.stopListening(this.model, 'change:' + name, this.render);
|
||||
|
||||
this.model.set(name,
|
||||
$.extend(true, val, this.innerModel.toJSON())
|
||||
);
|
||||
|
||||
this.listenTo(this.model, 'change:' + name, this.render);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.cleanup();
|
||||
this.$el.empty();
|
||||
|
||||
var self = this,
|
||||
initial_value = {},
|
||||
field = _.defaults(this.field.toJSON(), this.defaults),
|
||||
value = self.model.get(field['name']),
|
||||
innerFields = field['fields'];
|
||||
|
||||
this.$el.html(self.template(field)).addClass(field.name);
|
||||
|
||||
var $container = $(self.$el.find('.pgadmin-controls'));
|
||||
|
||||
_.each(innerFields, function(field) {
|
||||
initial_value[field['name']] = value[field['name']];
|
||||
});
|
||||
|
||||
self.innerModel.set(initial_value);
|
||||
|
||||
self.listenTo(self.innerModel, 'change', self.onInnerModelChange);
|
||||
|
||||
_.each(innerFields, function(fld) {
|
||||
|
||||
var f = new Backform.Field(
|
||||
_.extend({}, {
|
||||
id: fld['name'],
|
||||
name: fld['name'],
|
||||
control: fld['type'],
|
||||
label: fld['label'],
|
||||
})
|
||||
),
|
||||
cntr = new (f.get('control')) ({
|
||||
field: f,
|
||||
model: self.innerModel,
|
||||
});
|
||||
|
||||
cntr.render();
|
||||
|
||||
if(fld['type'] == 'checkbox') {
|
||||
if (fld['name'] == 'alt_option') {
|
||||
$container.append($('<div class="pg-el-sm-3 pg-el-xs-12"></div>').append(cntr.$el));
|
||||
} else {
|
||||
$container.append($('<div class="pg-el-sm-2 pg-el-xs-12"></div>').append(cntr.$el));
|
||||
}
|
||||
} else {
|
||||
$container.append($('<div class="pg-el-sm-5 pg-el-xs-12"></div>').append(cntr.$el));
|
||||
}
|
||||
|
||||
// We will keep track of all the controls rendered at the
|
||||
// moment.
|
||||
self.controls.push(cntr);
|
||||
|
||||
});
|
||||
|
||||
return self;
|
||||
},
|
||||
remove: function() {
|
||||
/* First do the clean up */
|
||||
this.cleanup();
|
||||
Backform.Control.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
return Backform;
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ module within the system.
|
||||
"""
|
||||
|
||||
import decimal
|
||||
import simplejson as json
|
||||
|
||||
import dateutil.parser as dateutil_parser
|
||||
from flask import current_app
|
||||
@ -31,7 +32,7 @@ class _Preference(object):
|
||||
|
||||
def __init__(
|
||||
self, cid, name, label, _type, default, help_str=None, min_val=None,
|
||||
max_val=None, options=None, select2=None
|
||||
max_val=None, options=None, select2=None, fields=None
|
||||
):
|
||||
"""
|
||||
__init__
|
||||
@ -54,6 +55,8 @@ class _Preference(object):
|
||||
:param max_val: maximum value
|
||||
:param options: options (Array of list objects)
|
||||
:param select2: select2 options (object)
|
||||
:param fields: field schema (if preference has more than one field to
|
||||
take input from user e.g. keyboardshortcut preference)
|
||||
|
||||
:returns: nothing
|
||||
"""
|
||||
@ -67,6 +70,7 @@ class _Preference(object):
|
||||
self.max_val = max_val
|
||||
self.options = options
|
||||
self.select2 = select2
|
||||
self.fields = fields
|
||||
|
||||
# Look into the configuration table to find out the id of the specific
|
||||
# preference.
|
||||
@ -137,6 +141,12 @@ class _Preference(object):
|
||||
if self._type == 'text':
|
||||
if res.value == '':
|
||||
return self.default
|
||||
if self._type == 'keyboardshortcut':
|
||||
try:
|
||||
return json.loads(res.value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return self.default
|
||||
|
||||
return res.value
|
||||
|
||||
@ -196,6 +206,14 @@ class _Preference(object):
|
||||
|
||||
if not has_value and self.select2 and not self.select2['tags']:
|
||||
return False, gettext("Invalid value for an options option.")
|
||||
elif self._type == 'keyboardshortcut':
|
||||
try:
|
||||
value = json.dumps(value)
|
||||
except Exception as e:
|
||||
current_app.logger.exeception(e)
|
||||
return False, gettext(
|
||||
"Invalid value for a keyboard shortcut option."
|
||||
)
|
||||
|
||||
pref = UserPrefTable.query.filter_by(
|
||||
pid=self.pid
|
||||
@ -231,7 +249,8 @@ class _Preference(object):
|
||||
'max_val': self.max_val,
|
||||
'options': self.options,
|
||||
'select2': self.select2,
|
||||
'value': self.get()
|
||||
'value': self.get(),
|
||||
'fields': self.fields,
|
||||
}
|
||||
return res
|
||||
|
||||
@ -371,7 +390,7 @@ class Preferences(object):
|
||||
def register(
|
||||
self, category, name, label, _type, default, min_val=None,
|
||||
max_val=None, options=None, help_str=None, category_label=None,
|
||||
select2=None
|
||||
select2=None, fields=None
|
||||
):
|
||||
"""
|
||||
register
|
||||
@ -393,6 +412,8 @@ class Preferences(object):
|
||||
:param help_str:
|
||||
:param category_label:
|
||||
:param select2: select2 control extra options
|
||||
:param fields: field schema (if preference has more than one field to
|
||||
take input from user e.g. keyboardshortcut preference)
|
||||
"""
|
||||
cat = self.__category(category, category_label)
|
||||
if name in cat['preferences']:
|
||||
@ -402,12 +423,13 @@ class Preferences(object):
|
||||
assert _type is not None, "Type for a preference cannot be none!"
|
||||
assert _type in (
|
||||
'boolean', 'integer', 'numeric', 'date', 'datetime',
|
||||
'options', 'multiline', 'switch', 'node', 'text'
|
||||
'options', 'multiline', 'switch', 'node', 'text',
|
||||
'keyboardshortcut'
|
||||
), "Type cannot be found in the defined list!"
|
||||
|
||||
(cat['preferences'])[name] = res = _Preference(
|
||||
cat['id'], name, label, _type, default, help_str, min_val,
|
||||
max_val, options, select2
|
||||
max_val, options, select2, fields
|
||||
)
|
||||
|
||||
return res
|
||||
|
@ -0,0 +1,434 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
define([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'pgadmin.backform',
|
||||
], function ($, Backbone, Backform) {
|
||||
describe('KeyboardshortcutControl', function () {
|
||||
let field, innerFields, control, model, event;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
innerFields = [
|
||||
{'name': 'key', 'type': 'keyCode', 'label': 'Key'},
|
||||
{'name': 'alt_option', 'type': 'checkbox',
|
||||
'label': 'Alt/Option'},
|
||||
{'name': 'control', 'type': 'checkbox',
|
||||
'label': 'Ctrl'},
|
||||
{'name': 'shift', 'type': 'checkbox', 'label': 'Shift'},
|
||||
];
|
||||
|
||||
model = new Backbone.Model({
|
||||
'shortcut': {
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
field = new Backform.Field({
|
||||
id: 'shortcut',
|
||||
name: 'shortcut',
|
||||
control: 'keyboardShortcut',
|
||||
label: 'Keyboard shortcut',
|
||||
fields: innerFields,
|
||||
});
|
||||
|
||||
control = new (field.get('control')) ({
|
||||
field: field,
|
||||
model: model,
|
||||
});
|
||||
|
||||
control.render();
|
||||
|
||||
});
|
||||
|
||||
describe('keyboardShortcut UI setup', function () {
|
||||
|
||||
it('keyboard shortcut control should be rendered with inner fields', function () {
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('onModelChange', function () {
|
||||
beforeEach((done) => {
|
||||
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
it('when model "key" value changes UI and innerModel should update new "key" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
expect(control.innerModel.get('key')).toEqual({
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
});
|
||||
|
||||
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||
|
||||
model.set(field.get('name'),
|
||||
$.extend(true, val, {
|
||||
'key': {
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('A');
|
||||
expect(control.innerModel.get('key')).toEqual({
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
});
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('control')).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when model "control" value changes UI and innerModel should update new "control" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('control')).toBeTruthy();
|
||||
|
||||
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||
|
||||
model.set(field.get('name'),
|
||||
$.extend(true, val, {
|
||||
'control': false
|
||||
})
|
||||
);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('control')).toBeFalsy();
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
expect(control.innerModel.get('key')).toEqual({
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
});
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when model "shift" value changes UI and innerModel should update new "shift" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||
|
||||
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||
|
||||
model.set(field.get('name'),
|
||||
$.extend(true, val, {
|
||||
'shift': true
|
||||
})
|
||||
);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('shift')).toBeTruthy();
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
expect(control.innerModel.get('key')).toEqual({
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
});
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('control')).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when model "alt_option" value changes UI and innerModel should update new "alt_option" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('alt_option')).toBeTruthy();
|
||||
|
||||
var val = $.extend(true, {}, model.get(field.get('name')));
|
||||
|
||||
model.set(field.get('name'),
|
||||
$.extend(true, val, {
|
||||
'alt_option': false
|
||||
})
|
||||
);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('alt_option')).toBeFalsy();
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
expect(control.innerModel.get('shift')).toBeFalsy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
expect(control.innerModel.get('key')).toEqual({
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
});
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
expect(control.innerModel.get('control')).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('onInnerModelChange', function () {
|
||||
beforeEach((done) => {
|
||||
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
it('when innerModel "key" value changes UI and model should update new "key" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
control.innerModel.set('key',
|
||||
{
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
}
|
||||
);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('A');
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
},
|
||||
});
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when innerModel "control" value changes UI and model should update new "control" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
control.innerModel.set('control', false);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeFalsy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': false,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when innerModel "shift" value changes UI and model should update new "shift" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
control.innerModel.set('shift', true);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeTruthy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': true,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when innerModel "alt_option" value changes UI and model should update new "alt_option" value', function (done) {
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeTruthy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': true,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
control.innerModel.set('alt_option', false);
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
// this should change
|
||||
expect(control.$el.find('input:checkbox[name="alt_option"]')[0].checked).toBeFalsy();
|
||||
expect(model.get(field.get('name'))).toEqual({
|
||||
'control': true,
|
||||
'shift': false,
|
||||
'alt_option': false,
|
||||
'key': {
|
||||
'key_code': 73,
|
||||
'char': 'I',
|
||||
},
|
||||
});
|
||||
|
||||
// below three should not change.
|
||||
expect(control.$el.find('input:checkbox[name="shift"]')[0].checked).toBeFalsy();
|
||||
|
||||
expect(control.$el.find('input:text[name="key"]')[0].value).toBe('I');
|
||||
|
||||
expect(control.$el.find('input:checkbox[name="control"]')[0].checked).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('remove keyboardShortcut control', function () {
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
spyOn(control, 'cleanup').and.callThrough();
|
||||
|
||||
});
|
||||
|
||||
it('when removed it should remove all of it\' controls', function () {
|
||||
|
||||
control.remove();
|
||||
|
||||
expect(control.cleanup).toHaveBeenCalled();
|
||||
|
||||
expect(control.controls.length).toBe(0);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
199
web/regression/javascript/backform_controls/keycode_spec.js
Normal file
199
web/regression/javascript/backform_controls/keycode_spec.js
Normal file
@ -0,0 +1,199 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
define([
|
||||
'backbone',
|
||||
'pgadmin.backform',
|
||||
], function (Backbone, Backform) {
|
||||
describe('keyCodeControl', function () {
|
||||
let field, control, model, event;
|
||||
|
||||
beforeEach(() => {
|
||||
model = new Backbone.Model({
|
||||
'key': {
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
}
|
||||
});
|
||||
|
||||
field = new Backform.Field({
|
||||
id: 'key',
|
||||
name: 'key',
|
||||
control: 'keyCode',
|
||||
label: 'Key',
|
||||
});
|
||||
|
||||
control = new (field.get('control')) ({
|
||||
field: field,
|
||||
model: model,
|
||||
});
|
||||
|
||||
control.render();
|
||||
|
||||
event = {
|
||||
which: -1,
|
||||
keyCode: -1,
|
||||
key: '',
|
||||
preventDefault: jasmine.createSpy('preventDefault'),
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
describe('onkeyDown', function () {
|
||||
|
||||
beforeEach((done) => {
|
||||
|
||||
spyOn(model, 'set').and.callThrough();
|
||||
|
||||
spyOn(control, 'onkeyDown').and.callThrough();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('when key with escapeKeyCode is pressed model should not update', function (done) {
|
||||
event.which = 16;
|
||||
event.keyCode = 16;
|
||||
event.key = 'Shift';
|
||||
|
||||
control.onkeyDown(event);
|
||||
|
||||
expect(control.onkeyDown).toHaveBeenCalled();
|
||||
|
||||
expect(model.set).not.toHaveBeenCalled();
|
||||
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
|
||||
expect(model.get('key')).toEqual({
|
||||
'key_code': 65,
|
||||
'char': 'A',
|
||||
});
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
expect(control.$el.find('input')[0].value).toBe('A');
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when key other than escapeKeyCode is pressed model should update', function (done) {
|
||||
event.which = 66;
|
||||
event.keyCode = 66;
|
||||
event.key = 'B';
|
||||
|
||||
control.onkeyDown(event);
|
||||
|
||||
expect(control.onkeyDown).toHaveBeenCalled();
|
||||
|
||||
expect(model.set).toHaveBeenCalled();
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
|
||||
expect(model.get('key')).toEqual({
|
||||
'key_code': 66,
|
||||
'char': 'B',
|
||||
});
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
expect(control.$el.find('input')[0].value).toBe('B');
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('onkeyUp', function () {
|
||||
|
||||
beforeEach((done) => {
|
||||
spyOn(control, 'preventEvent').and.callThrough();
|
||||
|
||||
event.stopPropagation = jasmine.createSpy('stopPropagation');
|
||||
|
||||
event.stopImmediatePropagation = jasmine.createSpy('stopImmediatePropagation');
|
||||
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
it('when key with escapeKeyCode is pressed and released event should be propagated', function (done) {
|
||||
event.which = 17;
|
||||
event.keyCode = 17;
|
||||
event.key = 'Ctrl';
|
||||
|
||||
control.preventEvent(event);
|
||||
|
||||
expect(control.preventEvent).toHaveBeenCalled();
|
||||
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
|
||||
expect(event.stopPropagation).not.toHaveBeenCalled();
|
||||
|
||||
expect(event.stopImmediatePropagation).not.toHaveBeenCalled();
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
expect(control.$el.find('input')[0].value).toBe('A');
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
it('when key other than escapeKeyCode is pressed and released event should not be propagated', function (done) {
|
||||
event.which = 66;
|
||||
event.keyCode = 66;
|
||||
event.key = 'B';
|
||||
|
||||
control.preventEvent(event);
|
||||
|
||||
expect(control.preventEvent).toHaveBeenCalled();
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
|
||||
expect(event.stopPropagation).toHaveBeenCalled();
|
||||
|
||||
expect(event.stopImmediatePropagation).toHaveBeenCalled();
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
expect(control.$el.find('input')[0].value).toBe('A');
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('onModelChange', function () {
|
||||
beforeEach((done) => {
|
||||
|
||||
done();
|
||||
|
||||
});
|
||||
|
||||
it('when model changes UI should update', function (done) {
|
||||
|
||||
expect(control.$el.find('input')[0].value).toBe('A');
|
||||
|
||||
model.set('key', {
|
||||
'key_code': 67,
|
||||
'char': 'C',
|
||||
});
|
||||
|
||||
// wait until UI updates.
|
||||
setTimeout(function() {
|
||||
expect(control.$el.find('input')[0].value).toBe('C');
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -12,6 +12,7 @@ module.exports = {
|
||||
jQuery: 'jquery',
|
||||
_: 'underscore',
|
||||
'underscore.string': 'underscore.string',
|
||||
'window.jQuery': 'jquery',
|
||||
}),
|
||||
],
|
||||
|
||||
@ -60,6 +61,13 @@ module.exports = {
|
||||
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
|
||||
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.2'),
|
||||
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3'),
|
||||
'spectrum': path.join(__dirname, './node_modules/spectrum-colorpicker/spectrum'),
|
||||
'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'),
|
||||
'bootstrap.datetimepicker': path.join(__dirname, './node_modules/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min'),
|
||||
'bootstrap.switch': path.join(__dirname, './node_modules/bootstrap-switch/dist/js/bootstrap-switch'),
|
||||
'backbone': path.join(__dirname, './node_modules/backbone/backbone'),
|
||||
'backform': path.join(__dirname, './node_modules/backform/src/backform'),
|
||||
'backgrid': path.join(__dirname, './node_modules/backgrid/lib/backgrid'),
|
||||
'sources': sourcesDir + '/js',
|
||||
'translations': regressionDir + '/javascript/fake_translations',
|
||||
'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
|
||||
@ -70,6 +78,8 @@ module.exports = {
|
||||
'pgadmin': sourcesDir + '/js/pgadmin',
|
||||
'pgadmin.sqlfoldcode': sourcesDir + '/js/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||
'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults',
|
||||
'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin',
|
||||
'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
|
||||
},
|
||||
},
|
||||
externals: {
|
||||
|
Loading…
Reference in New Issue
Block a user