diff --git a/web/pgadmin/preferences/static/css/preferences.css b/web/pgadmin/preferences/static/css/preferences.css index 9f8de62d1..df748bc6b 100644 --- a/web/pgadmin/preferences/static/css/preferences.css +++ b/web/pgadmin/preferences/static/css/preferences.css @@ -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; +} diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js index d025be153..7014d625d 100644 --- a/web/pgadmin/preferences/static/js/preferences.js +++ b/web/pgadmin/preferences/static/js/preferences.js @@ -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. diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js index 1005482ef..2bbaa174e 100644 --- a/web/pgadmin/static/js/backform.pgadmin.js +++ b/web/pgadmin/static/js/backform.pgadmin.js @@ -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([ + '', + '
', + ' <%=required ? "required" : ""%> />', + ' <% if (helpMessage && helpMessage.length) { %>', + ' <%=helpMessage%>', + ' <% } %>', + '
', + ].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([ + '', + '
', + '
', + ].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($('
').append(cntr.$el)); + } else { + $container.append($('
').append(cntr.$el)); + } + } else { + $container.append($('
').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; }); diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py index 2598da1a4..367caa9c1 100644 --- a/web/pgadmin/utils/preferences.py +++ b/web/pgadmin/utils/preferences.py @@ -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 diff --git a/web/regression/javascript/backform_controls/keyboardshortcut_spec.js b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js new file mode 100644 index 000000000..02f0715cb --- /dev/null +++ b/web/regression/javascript/backform_controls/keyboardshortcut_spec.js @@ -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); + + }); + + }); + + }); +}); \ No newline at end of file diff --git a/web/regression/javascript/backform_controls/keycode_spec.js b/web/regression/javascript/backform_controls/keycode_spec.js new file mode 100644 index 000000000..d71ef48c6 --- /dev/null +++ b/web/regression/javascript/backform_controls/keycode_spec.js @@ -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); + + }); + + }); + + }); +}); \ No newline at end of file diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js index 2767db8a8..966793a54 100644 --- a/web/webpack.test.config.js +++ b/web/webpack.test.config.js @@ -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: {