From 4aa1643cc40260079d41132a398b8ea4bf86f276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 15 Jun 2022 09:03:45 +0000 Subject: [PATCH] [REF] barcodes: simplify and improve barcode implementation - use keydown events all the time - remove dead code - simplify implementation Part-of: odoo/odoo#93387 --- .../barcodes/static/src/js/barcode_events.js | 178 +++++------------- .../barcodes/static/src/js/barcode_field.js | 34 +--- addons/barcodes/static/tests/barcode_tests.js | 117 ++++++------ 3 files changed, 110 insertions(+), 219 deletions(-) diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 6f2f678506c..7481cd43dd4 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -3,20 +3,9 @@ odoo.define('barcodes.BarcodeEvents', function(require) { var config = require('web.config'); var core = require('web.core'); -var mixins = require('web.mixins'); var session = require('web.session'); - -// For IE >= 9, use this, new CustomEvent(), instead of new Event() -function CustomEvent ( event, params ) { - params = params || { bubbles: false, cancelable: false, detail: undefined }; - var evt = document.createEvent( 'CustomEvent' ); - evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); - return evt; - } -CustomEvent.prototype = window.Event.prototype; - -var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { +var BarcodeEvents = core.Class.extend({ timeout: null, key_pressed: {}, buffered_key_events: [], @@ -37,38 +26,24 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { inputTimeOut: 800, init: function() { - mixins.PropertiesMixin.init.call(this); - // Keep a reference of the handler functions to use when adding and removing event listeners - this.__keydown_handler = _.bind(this.keydown_handler, this); - this.__keyup_handler = _.bind(this.keyup_handler, this); this.__handler = _.bind(this.handler, this); // Bind event handler once the DOM is loaded // TODO: find a way to be active only when there are listeners on the bus - $(_.bind(this.start, this, false)); + $(() => this.start()); // Mobile device detection this.isChromeMobile = config.device.isMobileDevice && navigator.userAgent.match(/Chrome/i); + }, - // Creates an input who will receive the barcode scanner value. - this.$barcodeInput = $('', { - name: 'barcode', - type: 'text', - css: { - 'position': 'fixed', - 'top': '50%', - 'transform': 'translateY(-50%)', - 'z-index': '-1', - 'opacity': '0', - }, - }); - // Avoid to show autocomplete for a non appearing input - this.$barcodeInput.attr('autocomplete', 'off'); - - this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut); + getCurrentString() { + if (this.$barcodeInput) { + return this.$barcodeInput.val(); + } + return this.buffered_key_events.reduce(function(memo, e) { return memo + e.key }, ''); }, handle_buffered_keys: function() { - var str = this.buffered_key_events.reduce(function(memo, e) { return memo + String.fromCharCode(e.which) }, ''); + let str = this.getCurrentString(); var match = str.match(this.regexp); if (match) { @@ -81,48 +56,11 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // Dispatch a barcode_scanned DOM event to elements that have barcode_events="true" set. if (this.buffered_key_events[0].target.getAttribute("barcode_events") === "true") $(this.buffered_key_events[0].target).trigger('barcode_scanned', barcode); - } else { - this.resend_buffered_keys(); } this.buffered_key_events = []; }, - resend_buffered_keys: function() { - var old_event, new_event; - for(var i = 0; i < this.buffered_key_events.length; i++) { - old_event = this.buffered_key_events[i]; - - if(old_event.which !== 13) { // ignore returns - // We do not create a 'real' keypress event through - // eg. KeyboardEvent because there are several issues - // with them that make them very different from - // genuine keypresses. Chrome per example has had a - // bug for the longest time that causes keyCode and - // charCode to not be set for events created this way: - // https://bugs.webkit.org/show_bug.cgi?id=16735 - var params = { - 'bubbles': old_event.bubbles, - 'cancelable': old_event.cancelable, - }; - new_event = $.Event('keypress', params); - new_event.viewArg = old_event.viewArg; - new_event.ctrl = old_event.ctrl; - new_event.alt = old_event.alt; - new_event.shift = old_event.shift; - new_event.meta = old_event.meta; - new_event.char = old_event.char; - new_event.key = old_event.key; - new_event.charCode = old_event.charCode; - new_event.keyCode = old_event.keyCode || old_event.which; // Firefox doesn't set keyCode for keypresses, only keyup/down - new_event.which = old_event.which; - new_event.dispatched_by_barcode_reader = true; - - $(old_event.target).trigger(new_event); - } - } - }, - element_is_editable: function(element) { return $(element).is('input,textarea,[contenteditable="true"]'); }, @@ -138,6 +76,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { e.key === "Backspace" || e.key === "Delete" || e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown" || + e.key === "Shift" || e.key === "Unidentified" || /F\d\d?/.test(e.key)) { return true; } else { @@ -145,46 +84,27 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { } }, - // The keydown and keyup handlers are here to disallow key - // repeat. When preventDefault() is called on a keydown event - // the keypress that normally follows is cancelled. - keydown_handler: function(e){ - if (this.key_pressed[e.which]) { - e.preventDefault(); - } else { - this.key_pressed[e.which] = true; - } - }, - - keyup_handler: function(e){ - this.key_pressed[e.which] = false; - }, - handler: function(e){ - // Don't catch events we resent - if (e.dispatched_by_barcode_reader) - return; // Don't catch non-printable keys for which Firefox triggers a keypress - if (this.is_special_key(e)) + if (this.is_special_key(e)) { return; + } // Don't catch keypresses which could have a UX purpose (like shortcuts) - if (e.ctrlKey || e.metaKey || e.altKey) - return; - // Don't catch Return when nothing is buffered. This way users - // can still use Return to 'click' on focused buttons or links. - if (e.which === 13 && this.buffered_key_events.length === 0) + if (e.ctrlKey || e.metaKey || e.altKey) { return; + } // Don't catch events targeting elements that are editable because we // have no way of redispatching 'genuine' key events. Resent events // don't trigger native event handlers of elements. So this means that // our fake events will not appear in eg. an element. - if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true") + if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true") { return; + } // Catch and buffer the event - this.buffered_key_events.push(e); - e.preventDefault(); - e.stopImmediatePropagation(); + if (e.key !== "Enter") { + this.buffered_key_events.push(e); + } // Handle buffered keys immediately if the keypress marks the end // of a barcode or after x milliseconds without a new keypress @@ -215,40 +135,15 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { _listenBarcodeScanner: function (e) { if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' + '[type="email"], [type="number"], [type="password"], [type="tel"], [type="search"]').length) { - $('body').append(this.$barcodeInput); this.$barcodeInput.focus(); } if (this.$barcodeInput.is(":focus")) { - // Handle buffered keys immediately if the keypress marks the end - // of a barcode or after x milliseconds without a new keypress. - clearTimeout(this.timeout); - // On chrome mobile, e.which only works for some special characters like ENTER or TAB. - if (String.fromCharCode(e.which).match(this.suffix)) { - this._handleBarcodeValue(e); - } else { - this.timeout = setTimeout(this._handleBarcodeValue.bind(this, e), - this.max_time_between_keys_in_ms); - } + this.handler(e); // if the barcode input doesn't receive keydown for a while, remove it. this.__blurBarcodeInput(); } }, - /** - * Retrieves the barcode value from the temporary input element. - * This checks this value and trigger it on the bus. - * - * @private - * @param {jQuery.Event} keydown event - */ - _handleBarcodeValue: function (e) { - var barcodeValue = this.$barcodeInput.val(); - if (barcodeValue.match(this.regexp)) { - core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]); - this._blurBarcodeInput(); - } - }, - /** * Removes the value and focus from the barcode input. * If nothing happens, the focus will be lost and @@ -262,7 +157,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { this.$barcodeInput.val('').blur(); }, - start: function(prevent_key_repeat){ + start: function(){ // Chrome Mobile isn't triggering keypress event. // This is marked as Legacy in the DOM-Level-3 Standard. // See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types @@ -270,20 +165,35 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // all other cases. // In master, we could remove the behavior with keypress and only use keydown. if (this.isChromeMobile) { + // Creates an input who will receive the barcode scanner value. + this.$barcodeInput = $('', { + name: 'barcode', + type: 'text', + css: { + 'position': 'fixed', + 'top': '50%', + 'transform': 'translateY(-50%)', + 'z-index': '-1', + 'opacity': '0', + }, + }); + // Avoid to show autocomplete for a non appearing input + this.$barcodeInput.attr('autocomplete', 'off'); + + this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut); + + + $('body').append(this.$barcodeInput); + core.bus.on('barcode_scanned', null, () => this._blurBarcodeInput()); + $('body').on("keydown", this._listenBarcodeScanner.bind(this)); } else { - $('body').bind("keypress", this.__handler); - } - if (prevent_key_repeat === true) { - $('body').bind("keydown", this.__keydown_handler); - $('body').bind('keyup', this.__keyup_handler); + $('body').bind("keydown", this.__handler); } }, stop: function(){ - $('body').off("keypress", this.__handler); - $('body').off("keydown", this.__keydown_handler); - $('body').off('keyup', this.__keyup_handler); + $('body').off("keydown", this.__handler); }, }); diff --git a/addons/barcodes/static/src/js/barcode_field.js b/addons/barcodes/static/src/js/barcode_field.js index b5786f8a161..dee98412975 100644 --- a/addons/barcodes/static/src/js/barcode_field.js +++ b/addons/barcodes/static/src/js/barcode_field.js @@ -4,18 +4,13 @@ odoo.define('barcodes.field', function(require) { var AbstractField = require('web.AbstractField'); var basicFields = require('web.basic_fields'); var fieldRegistry = require('web.field_registry'); -var BarcodeEvents = require('barcodes.BarcodeEvents').BarcodeEvents; // Field in which the user can both type normally and scan barcodes var FieldFloatScannable = basicFields.FieldFloat.extend({ events: _.extend({}, basicFields.FieldFloat.prototype.events, { - // The barcode_events component intercepts keypresses and releases them when it - // appears they are not part of a barcode. But since released keypresses don't - // trigger native behaviour (like characters input), we must simulate it. - keypress: '_onKeypress', + barcode_scanned: '_onBarcodeScan', }), - //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -35,28 +30,11 @@ var FieldFloatScannable = basicFields.FieldFloat.extend({ // Handlers //-------------------------------------------------------------------------- - /** - * @private - * @param {KeyboardEvent} e - */ - _onKeypress: function (e) { - /* only simulate a keypress if it has been previously prevented */ - if (e.dispatched_by_barcode_reader !== true) { - if (!BarcodeEvents.is_special_key(e)) { - e.preventDefault(); - } - return; - } - var character = String.fromCharCode(e.which); - var current_str = e.target.value; - var str_before_carret = current_str.substring(0, e.target.selectionStart); - var str_after_carret = current_str.substring(e.target.selectionEnd); - e.target.value = str_before_carret + character + str_after_carret; - var new_carret_index = str_before_carret.length + character.length; - e.target.setSelectionRange(new_carret_index, new_carret_index); - // trigger an 'input' event to notify the widget that it's value changed - $(e.target).trigger('input'); - }, + _onBarcodeScan() { + // trigger an 'input' event to make sure that the widget is call + // notifyChanges + this.$input.trigger('input'); + } }); // Field to use scan barcodes diff --git a/addons/barcodes/static/tests/barcode_tests.js b/addons/barcodes/static/tests/barcode_tests.js index 8ba4b92c3a7..1740a6321bc 100644 --- a/addons/barcodes/static/tests/barcode_tests.js +++ b/addons/barcodes/static/tests/barcode_tests.js @@ -11,6 +11,26 @@ var testUtils = require('web.test_utils'); var createView = testUtils.createView; var triggerKeypressEvent = testUtils.dom.triggerKeypressEvent; +var triggerEvent = testUtils.dom.triggerEvent; +var core = require('web.core'); + +function simulateBarCode(chars, target = document.body) { + for (let char of chars) { + let keycode; + if (char === 'Enter') { + keycode = $.ui.keyCode.ENTER; + } else if (char === "Tab") { + keycode = $.ui.keyCode.TAB; + } else { + keycode = char.charCodeAt(0); + } + triggerEvent(target, 'keydown', { + key: char, + keyCode: keycode, + which: keycode, + }); + } +} QUnit.module('Barcodes', { beforeEach: function () { @@ -39,6 +59,7 @@ QUnit.module('Barcodes', { fields: { name: {string : "Product name", type: "char"}, int_field: {string : "Integer", type: "integer"}, + int_field_2: {string : "Integer", type: "integer"}, barcode: {string: "Barcode", type: "char"}, }, records: [ @@ -80,9 +101,9 @@ QUnit.test('Button with barcode_trigger', async function (assert) { }); // O-BTN.doit - _.each(['O','-','B','T','N','.','d','o','i','t','Enter'], triggerKeypressEvent); + simulateBarCode(['O','-','B','T','N','.','d','o','i','t','Enter']); // O-BTN.dothat (should not call execute_action as the button isn't visible) - _.each(['O','-','B','T','N','.','d','o','t','h','a','t','Enter'], triggerKeypressEvent); + simulateBarCode(['O','-','B','T','N','.','d','o','t','h','a','t','Enter']); await testUtils.nextTick(); assert.verifySteps([], "no warning should be displayed"); @@ -107,26 +128,26 @@ QUnit.test('edit, save and cancel buttons', async function (assert) { }); // O-CMD.EDIT - _.each(["O","-","C","M","D",".","E","D","I","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","E","D","I","T","Enter"], document.body); await testUtils.nextTick(); assert.containsOnce(form, ".o_form_editable", "should have switched to 'edit' mode"); // dummy change to check that it actually saves await testUtils.fields.editInput(form.$('.o_field_widget'), 'test'); // O-CMD.SAVE - _.each(["O","-","C","M","D",".","S","A","V","E","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","S","A","V","E","Enter"], document.body); await testUtils.nextTick(); assert.containsOnce(form, ".o_form_readonly", "should have switched to 'readonly' mode"); assert.verifySteps(['save'], 'should have saved'); // O-CMD.EDIT - _.each(["O","-","C","M","D",".","E","D","I","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","E","D","I","T","Enter"], document.body); await testUtils.nextTick(); // dummy change to check that it correctly discards await testUtils.fields.editInput(form.$('.o_field_widget'), 'test'); // O-CMD.CANCEL - _.each(["O","-","C","M","D",".","D","I","S","C","A","R","D","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","D","I","S","C","A","R","D","Enter"], document.body); await testUtils.nextTick(); assert.containsOnce(form, ".o_form_readonly", "should have switched to 'readonly' mode"); @@ -152,19 +173,19 @@ QUnit.test('pager buttons', async function (assert) { assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet'); // O-CMD.PAGER-NEXT - _.each(["O","-","C","M","D",".","N","E","X","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","N","E","X","T","Enter"]); await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget').text(), 'Cabinet with Doors'); // O-CMD.PAGER-PREV - _.each(["O","-","C","M","D",".","P","R","E","V","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","P","R","E","V","Enter"]); await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet'); // O-CMD.PAGER-LAST - _.each(["O","-","C","M","D",".","P","A","G","E","R","-","L","A","S","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","L","A","S","T","Enter"]); await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget').text(), 'Cabinet with Doors'); // O-CMD.PAGER-FIRST - _.each(["O","-","C","M","D",".","P","A","G","E","R","-","F","I","R","S","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","P","A","G","E","R","-","F","I","R","S","T","Enter"]); await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget').text(), 'Large Cabinet'); @@ -207,13 +228,13 @@ QUnit.test('do no update form twice after a command barcode scanned', async func assert.verifySteps(['read'], "update should not have been called yet"); // switch to next record - _.each(["O","-","C","M","D",".","N","E","X","T","Enter"], triggerKeypressEvent); + simulateBarCode(["O","-","C","M","D",".","N","E","X","T","Enter"]); await testUtils.nextTick(); // a first update is done to reload the data (thus followed by a read), but // update shouldn't be called afterwards assert.verifySteps(['update', 'read']); - _.each(['5','4','3','9','8','2','6','7','1','2','5','2','Enter'], triggerKeypressEvent); + simulateBarCode(['5','4','3','9','8','2','6','7','1','2','5','2','Enter']); await testUtils.nextTick(); // a real barcode has been scanned -> an update should be requested (with // option reload='false', so it isn't followed by a read) @@ -225,16 +246,15 @@ QUnit.test('do no update form twice after a command barcode scanned', async func }); QUnit.test('widget field_float_scannable', async function (assert) { - var done = assert.async(); - assert.expect(11); - var delay = barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms; barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = 0; this.data.product.records[0].int_field = 4; - this.data.product.onchanges = { - int_field: function () {}, - }; + + function _onBarcodeScanned (code) { + assert.step(`barcode scanned ${code}`) + } + core.bus.on('barcode_scanned', null, _onBarcodeScanned); var form = await createView({ View: FormView, @@ -243,6 +263,7 @@ QUnit.test('widget field_float_scannable', async function (assert) { arch: '
' + '' + '' + + '' + '', mockRPC: function (route, args) { if (args.method === 'onchange') { @@ -266,34 +287,25 @@ QUnit.test('widget field_float_scannable', async function (assert) { // simulates keypress events in the input to replace 0.00 by 26 (should not trigger onchanges) form.$('.o_field_widget[name=int_field]').focus(); + + // we check here that a scan on the fieldflotscannable widget triggers a + // barcode event + simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], document.activeElement) + await testUtils.nextTick(); + assert.verifySteps(['barcode scanned 60164785']); assert.strictEqual(form.$('.o_field_widget[name=int_field]').get(0), document.activeElement, "int field should be focused"); - form.$('.o_field_widget[name=int_field]').trigger({type: 'keypress', which: 50, keyCode: 50}); // 2 + + // we check here that a scan on the field without widget does not trigger a + // barcode event + form.$('.o_field_widget[name=int_field_2]').focus(); + simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], document.activeElement) await testUtils.nextTick(); - assert.strictEqual(form.$('.o_field_widget[name=int_field]').get(0), document.activeElement, - "int field should still be focused"); - form.$('.o_field_widget[name=int_field]').trigger({type: 'keypress', which: 54, keyCode: 54}); // 6 - await testUtils.nextTick(); - assert.strictEqual(form.$('.o_field_widget[name=int_field]').get(0), document.activeElement, - "int field should still be focused"); + assert.verifySteps([]); - setTimeout(async function () { - assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '426', - "should display the correct value in edit"); - assert.strictEqual(form.$('.o_field_widget[name=int_field]').get(0), document.activeElement, - "int field should still be focused"); - - assert.verifySteps([], 'should not have done any onchange RPC'); - - form.$('.o_field_widget[name=int_field]').trigger('change'); // should trigger the onchange - await testUtils.nextTick(); - - assert.verifySteps(['onchange'], 'should have done the onchange RPC'); - - form.destroy(); - barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay; - done(); - }); + form.destroy(); + barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay; + core.bus.off('barcode_scanned', null, _onBarcodeScanned) }); QUnit.test('widget barcode_handler', async function (assert) { @@ -335,7 +347,7 @@ QUnit.test('widget barcode_handler', async function (assert) { assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '0', "initial value should be correct"); - _.each(['5','4','3','9','8','2','6','7','1','2','5','2','Enter'], triggerKeypressEvent); + simulateBarCode(['5','4','3','9','8','2','6','7','1','2','5','2','Enter']); await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '1', "value should have been incremented"); @@ -404,17 +416,17 @@ QUnit.test('specification of widget barcode_handler', async function (assert) { "one2many should contain 2 rows"); // scan twice product 1 - _.each(['1','2','3','4','5','6','7','8','9','0','Enter'], triggerKeypressEvent); + simulateBarCode(['1','2','3','4','5','6','7','8','9','0','Enter']); await testUtils.nextTick(); assert.strictEqual(form.$('.o_data_row:first .o_data_cell:nth(1)').text(), '1', "quantity of line one should have been incremented"); - _.each(['1','2','3','4','5','6','7','8','9','0','Enter'], triggerKeypressEvent); + simulateBarCode(['1','2','3','4','5','6','7','8','9','0','Enter']); await testUtils.nextTick(); assert.strictEqual(form.$('.o_data_row:first .o_data_cell:nth(1)').text(), '2', "quantity of line one should have been incremented"); // scan once product 2 - _.each(['0','9','8','7','6','5','4','3','2','1','Enter'], triggerKeypressEvent); + simulateBarCode(['0','9','8','7','6','5','4','3','2','1','Enter']); await testUtils.nextTick(); assert.strictEqual(form.$('.o_data_row:nth(1) .o_data_cell:nth(1)').text(), '1', "quantity of line one should have been incremented"); @@ -479,7 +491,7 @@ QUnit.test('specification of widget barcode_handler with keypress and notifyChan mode: 'edit', }, }); - _.each(['1','2','3','4','5','6','7','8','9','0','Enter'], triggerKeypressEvent); + simulateBarCode(['1','2','3','4','5','6','7','8','9','0','Enter']); await testUtils.nextTick(); // Quantity listener should open a dialog. triggerKeypressEvent('5'); @@ -550,16 +562,7 @@ QUnit.test('barcode_scanned only trigger error for active view', async function // We do not trigger on the body since modal and // form view are both inside it. - function modalTriggerKeypressEvent(char) { - var keycode; - if (char === "Enter") { - keycode = $.ui.keyCode.ENTER; - } else { - keycode = char.charCodeAt(0); - } - return $('.modal').trigger($.Event('keypress', {which: keycode, keyCode: keycode})); - } - _.each(['O','-','B','T','N','.','c','a','n','c','e','l','Enter'], modalTriggerKeypressEvent); + simulateBarCode(['O','-','B','T','N','.','c','a','n','c','e','l','Enter'], $('.modal')); await testUtils.nextTick(); assert.verifySteps(['danger'], "only one event should be triggered"); form.destroy();