diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js
index 730d77b5c42..7ebe3857406 100644
--- a/addons/point_of_sale/static/src/js/Chrome.js
+++ b/addons/point_of_sale/static/src/js/Chrome.js
@@ -7,7 +7,6 @@ odoo.define('point_of_sale.Chrome', function(require) {
const BarcodeParser = require('barcodes.BarcodeParser');
const PosComponent = require('point_of_sale.PosComponent');
const NumberBuffer = require('point_of_sale.NumberBuffer');
- const PopupControllerMixin = require('point_of_sale.PopupControllerMixin');
const Registries = require('point_of_sale.Registries');
const IndependentToOrderScreen = require('point_of_sale.IndependentToOrderScreen');
const { identifyError, batched } = require('point_of_sale.utils');
@@ -32,7 +31,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
/**
* Chrome is the root component of the PoS App.
*/
- class Chrome extends PopupControllerMixin(PosComponent) {
+ class Chrome extends PosComponent {
setup() {
super.setup();
useExternalListener(window, 'beforeunload', this._onBeforeUnload);
@@ -249,7 +248,7 @@ odoo.define('point_of_sale.Chrome', function(require) {
openCashControl() {
if (this.shouldShowCashControl()) {
- this.showPopup('CashOpeningPopup', { notEscapable: true });
+ this.showPopup('CashOpeningPopup', { cancelKey: false });
}
}
diff --git a/addons/point_of_sale/static/src/js/PopupControllerMixin.js b/addons/point_of_sale/static/src/js/PopupControllerMixin.js
deleted file mode 100644
index bcafe4a6a06..00000000000
--- a/addons/point_of_sale/static/src/js/PopupControllerMixin.js
+++ /dev/null
@@ -1,45 +0,0 @@
-odoo.define('point_of_sale.PopupControllerMixin', function(require) {
- 'use strict';
-
- const { useListener } = require("@web/core/utils/hooks");
-
- const { useState } = owl;
-
- /**
- * Allows the component declared with this mixin the ability show popup dynamically,
- * provided the following:
- * 1. The following element is declared in the template. It is where the Popup will be rendered.
- * ``
- * 2. The component should trigger `show-popup` event to show the popup and `close-popup` event
- * to close. In PosComponent, `showPopup` is conveniently declared to satisfy this requirement.
- * @param {Function} x class definition to mix with during extension
- */
- const PopupControllerMixin = x =>
- class extends x {
- setup() {
- super.setup();
- useListener('show-popup', this.__showPopup);
- useListener('close-popup', this.__closePopup);
-
- this.popup = useState({ isShown: false, name: null, component: null });
- this.popupProps = {}; // We want to avoid making the props to become Proxy!
- }
- __showPopup(event) {
- const { name, props, resolve } = event.detail;
- const popupConstructor = this.constructor.components[name];
- if (popupConstructor.dontShow) {
- resolve();
- return;
- }
- this.popup.isShown = true;
- this.popup.name = name;
- this.popup.component = popupConstructor;
- this.popupProps = Object.assign({}, props, { resolve });
- }
- __closePopup() {
- this.popup.isShown = false;
- }
- };
-
- return PopupControllerMixin;
-});
diff --git a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
index ce9d00ae85f..ce88df5fbc2 100644
--- a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
+++ b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js
@@ -2,8 +2,7 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
'use strict';
const PosComponent = require('point_of_sale.PosComponent');
-
- const { useExternalListener } = owl;
+ const { useBus } = require('@web/core/utils/hooks');
/**
* Implement this abstract class by extending it like so:
@@ -33,22 +32,24 @@ odoo.define('point_of_sale.AbstractAwaitablePopup', function (require) {
class AbstractAwaitablePopup extends PosComponent {
setup() {
super.setup();
- if (!this.props.notEscapable) {
- useExternalListener(window, 'keyup', this._cancelAtEscape);
+ if (this.props.confirmKey) {
+ useBus(this.env.posbus, `confirm-popup-${this.props.id}`, this.confirm);
+ }
+ if (this.props.cancelKey) {
+ useBus(this.env.posbus, `cancel-popup-${this.props.id}`, this.cancel);
}
}
async confirm() {
- this.props.resolve({ confirmed: true, payload: await this.getPayload() });
- this.trigger('close-popup');
+ this.env.posbus.trigger('close-popup', {
+ popupId: this.props.id,
+ response: { confirmed: true, payload: await this.getPayload() },
+ });
}
cancel() {
- this.props.resolve({ confirmed: false, payload: null });
- this.trigger('close-popup');
- }
- _cancelAtEscape(event) {
- if (event.key === 'Escape') {
- this.cancel();
- }
+ this.env.posbus.trigger('close-popup', {
+ popupId: this.props.id,
+ response: { confirmed: false, payload: null },
+ });
}
/**
* Override this in the concrete popup implementation to set the
diff --git a/addons/point_of_sale/static/src/js/Popups/PosPopupController.js b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js
new file mode 100644
index 00000000000..1a4eba4c962
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/Popups/PosPopupController.js
@@ -0,0 +1,120 @@
+odoo.define('point_of_sale.PosPopupController', function(require) {
+ 'use strict';
+
+ const Registries = require('point_of_sale.Registries');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useBus } = require('@web/core/utils/hooks');
+
+ /**
+ * This component is responsible in controlling the popups. It does so
+ * by coordinating with them thru the `env.posbus`. The basic steps follow:
+ * 1. `showPopup` method triggers `show-popup` event resulting to the
+ * mounting of the requested popup.
+ * 2. When the popup is shown, the `confirm`/`cancel` method of the popup
+ * will be called after the popup is used. `confirming`/`cancelling`
+ * will trigger the `close-popup`, which this component also listens to,
+ * resulting to closing of the popup.
+ *
+ * Furthermore, Pressing `confirmKey`/`cancelKey` which defaults to
+ * 'Enter'/'Escape', will automatically `confirm`/`cancel` the `topPopup`.
+ * This behavior is accomplished by listening to `keyup` event of the window.
+ * When the `confirmKey`/`cancelKey` of the `topPopup` is pressed,
+ * 'cancel-popup-{top-popup-id}'/'confirm-popup-{top-popup-id}' will be triggered
+ * and since the popup is listening to that event (@see AbstractAwaitablePopup),
+ * it will result to the call of `confirm`/`cancel` method.
+ *
+ * @typedef {{ id: number, resolve: Function, keepBehind?: boolean, cancelKey?: string, confirmKey?: string }} BasePopupProps
+ * @typedef {{ name: string, component: AbstractAwaitablePopup, props: BasePopupProps, key: string }} Popup
+ */
+ class PosPopupController extends PosComponent {
+ setup() {
+ super.setup();
+ useBus(this.env.posbus, 'show-popup', this._showPopup);
+ useBus(this.env.posbus, 'close-popup', this._closePopup);
+ owl.useExternalListener(window, 'keyup', this._onWindowKeyup);
+ this.popups = owl.useState([]);
+ }
+ _showPopup(event) {
+ let { id, name, props, resolve } = event.detail;
+ props = Object.assign(props || {}, { id, resolve });
+ const component = this.constructor.components[name];
+ if (!component) {
+ throw new Error(`'${name}' is not found. Make sure the file is loaded and the component is properly registered using 'Registries.Component.add'.`);
+ }
+ if (component.dontShow) {
+ resolve();
+ return;
+ }
+ this.popups.push({
+ name,
+ component,
+ props: this._constructPopupProps(component, props),
+ key: `${name}-${id}`,
+ });
+ }
+ _closePopup(event) {
+ const { popupId, response } = event.detail;
+ const index = this.popups.findIndex((popup) => popup.props.id == popupId);
+ if (index != -1) {
+ const popup = this.popups[index];
+ popup.props.resolve(response);
+ this.popups.splice(index, 1);
+ }
+ }
+ _onWindowKeyup(event) {
+ if (!this.topPopup) return;
+ if (event.key === this.topPopup.props.cancelKey) {
+ this.env.posbus.trigger(`cancel-popup-${this.topPopup.props.id}`);
+ } else if (event.key === this.topPopup.props.confirmKey) {
+ this.env.posbus.trigger(`confirm-popup-${this.topPopup.props.id}`);
+ }
+ }
+ /**
+ * A popup can be cancelled/confirmed with 'Escape'/'Enter' key by default.
+ * Also, if it's not the top popup, it is hidden from the view.
+ * This can be overridden by the default props of the popop component
+ * and the props used in requesting to show the popup.
+ *
+ * @param {AbstractAwaitablePopup} popupComponent
+ * @param {Object} props
+ * @returns {BasePopupProps}
+ */
+ _constructPopupProps(popupComponent, props) {
+ const defaultProps = popupComponent.defaultProps || {};
+ return Object.assign(
+ {
+ keepBehind: false,
+ cancelKey: 'Escape',
+ confirmKey: 'Enter',
+ },
+ defaultProps,
+ props
+ );
+ }
+ /**
+ * @returns {boolean} Hide the element of this component when this returns false.
+ */
+ isShown() {
+ return this.popups.length > 0;
+ }
+ get topPopup() {
+ return this.popups[this.popups.length - 1];
+ }
+ /**
+ * By default, only show the top popup. But always show a popup if
+ * `keepBehind` props is true. Meaning, if you have 2 popups, and
+ * the bottom popup has `keepBehind = true`, then the bottom popup
+ * will be visible if it's not blocked in the view by the top popup.
+ *
+ * @param {Popup} popup
+ * @returns {boolean}
+ */
+ shouldShow(popup) {
+ return this.topPopup === popup || popup.props.keepBehind;
+ }
+ }
+ PosPopupController.template = 'point_of_sale.PosPopupController';
+ Registries.Component.add(PosPopupController);
+
+ return PosPopupController;
+});
diff --git a/addons/point_of_sale/static/src/js/PosComponent.js b/addons/point_of_sale/static/src/js/PosComponent.js
index 51b82078721..2f969a687ad 100644
--- a/addons/point_of_sale/static/src/js/PosComponent.js
+++ b/addons/point_of_sale/static/src/js/PosComponent.js
@@ -4,6 +4,8 @@ odoo.define('point_of_sale.PosComponent', function (require) {
const { LegacyComponent } = require("@web/legacy/legacy_component");
const { onRendered } = owl;
+ let nextId = 0;
+
class PosComponent extends LegacyComponent {
setup() {
onRendered(() => {
@@ -36,7 +38,7 @@ odoo.define('point_of_sale.PosComponent', function (require) {
*/
showPopup(name, props) {
return new Promise((resolve) => {
- this.trigger('show-popup', { name, props, resolve });
+ this.env.posbus.trigger('show-popup', { name, props, resolve, id: nextId++ });
});
}
showTempScreen(name, props) {
diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
index 8054aa9f752..0c0799fdffa 100644
--- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js
@@ -14,7 +14,6 @@ odoo.define('point_of_sale.RefundButton', function (require) {
_onClick() {
const partner = this.env.pos.get_order().get_partner();
const searchDetails = partner ? { fieldName: 'PARTNER', searchTerm: partner.name } : {};
- this.trigger('close-popup');
this.showScreen('TicketScreen', {
ui: { filter: 'SYNCED', searchDetails },
destinationOrder: this.env.pos.get_order(),
diff --git a/addons/point_of_sale/static/src/xml/Chrome.xml b/addons/point_of_sale/static/src/xml/Chrome.xml
index a721a6647cb..ae9164120d5 100644
--- a/addons/point_of_sale/static/src/xml/Chrome.xml
+++ b/addons/point_of_sale/static/src/xml/Chrome.xml
@@ -62,10 +62,7 @@
-
+
diff --git a/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml b/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml
index 377af74f076..053bc04a5e8 100644
--- a/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml
@@ -2,7 +2,6 @@
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml b/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml
index cb6544456f0..60e518373d6 100644
--- a/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml
@@ -1,7 +1,6 @@
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml
index 7a2a390ac46..4b243ea36dd 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml
@@ -2,7 +2,6 @@
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ConfirmPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ConfirmPopup.xml
index f3b22b61785..f2fedfaeab2 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ConfirmPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ConfirmPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ControlButtonPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ControlButtonPopup.xml
index aef6a15279a..a92ad0a5301 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ControlButtonPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ControlButtonPopup.xml
@@ -2,10 +2,10 @@
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ErrorBarcodePopup.xml b/addons/point_of_sale/static/src/xml/Popups/ErrorBarcodePopup.xml
index 6b455fcaa65..7e4f9303a3e 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ErrorBarcodePopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ErrorBarcodePopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ErrorPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ErrorPopup.xml
index 0f2e19e35e7..e8882d52594 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ErrorPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ErrorPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ErrorTracebackPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ErrorTracebackPopup.xml
index 394f1b9ec29..157f1bb10d8 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ErrorTracebackPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ErrorTracebackPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml b/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml
index 3a6d191f35f..4c9eeafb815 100644
--- a/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/NumberPopup.xml
@@ -2,7 +2,6 @@
-
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/OfflineErrorPopup.xml b/addons/point_of_sale/static/src/xml/Popups/OfflineErrorPopup.xml
index 9950bc3d28d..6be9723f2d0 100644
--- a/addons/point_of_sale/static/src/xml/Popups/OfflineErrorPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/OfflineErrorPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/OrderImportPopup.xml b/addons/point_of_sale/static/src/xml/Popups/OrderImportPopup.xml
index b2f142b9536..ce286454cd2 100644
--- a/addons/point_of_sale/static/src/xml/Popups/OrderImportPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/OrderImportPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml b/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml
new file mode 100644
index 00000000000..78392b7ef8f
--- /dev/null
+++ b/addons/point_of_sale/static/src/xml/Popups/PosPopupController.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml
index 55ef49a16d7..3188264256a 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ProductConfiguratorPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/ProductInfoPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ProductInfoPopup.xml
index 9ccfbdcf204..6398d626dd4 100644
--- a/addons/point_of_sale/static/src/xml/Popups/ProductInfoPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/ProductInfoPopup.xml
@@ -1,7 +1,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/SelectionPopup.xml b/addons/point_of_sale/static/src/xml/Popups/SelectionPopup.xml
index 249ac8c6815..12add8473ab 100644
--- a/addons/point_of_sale/static/src/xml/Popups/SelectionPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/SelectionPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/TextAreaPopup.xml b/addons/point_of_sale/static/src/xml/Popups/TextAreaPopup.xml
index a142c995719..2e2c67a3212 100644
--- a/addons/point_of_sale/static/src/xml/Popups/TextAreaPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/TextAreaPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/src/xml/Popups/TextInputPopup.xml b/addons/point_of_sale/static/src/xml/Popups/TextInputPopup.xml
index cc28d70638c..3cfb2c21364 100644
--- a/addons/point_of_sale/static/src/xml/Popups/TextInputPopup.xml
+++ b/addons/point_of_sale/static/src/xml/Popups/TextInputPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/point_of_sale/static/tests/unit/test_PosPopupController.js b/addons/point_of_sale/static/tests/unit/test_PosPopupController.js
new file mode 100644
index 00000000000..51e9e39914c
--- /dev/null
+++ b/addons/point_of_sale/static/tests/unit/test_PosPopupController.js
@@ -0,0 +1,165 @@
+odoo.define('point_of_sale.tests.PosPopupController', function(require) {
+ 'use strict';
+
+ const PosPopupController = require('point_of_sale.PosPopupController');
+ const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const makeTestEnvironment = require('web.test_env');
+ const testUtils = require('web.test_utils');
+ const Registries = require('point_of_sale.Registries');
+ const { mount } = require('@web/../tests/helpers/utils');
+
+ const { EventBus, useSubEnv, xml } = owl;
+
+ QUnit.module('unit tests for PosPopupController', {
+ before() {
+ Registries.Component.freeze();
+
+ // Note that we are creating new popups here to decouple this test from the pos app.
+ class CustomPopup1 extends AbstractAwaitablePopup {}
+ CustomPopup1.template = xml/* html */`
+
+ `;
+
+ class CustomPopup2 extends AbstractAwaitablePopup {}
+ CustomPopup2.template = xml/* html */`
+
+ `;
+
+ PosPopupController.components = { CustomPopup1, CustomPopup2 };
+ },
+ });
+
+ QUnit.test('allow multiple popups at the same time', async function(assert) {
+ assert.expect(12);
+
+ class Root extends PosComponent {
+ setup() {
+ super.setup();
+ useSubEnv({
+ isDebug: () => false,
+ posbus: new EventBus(),
+ });
+ }
+ }
+ Root.env = makeTestEnvironment();
+ Root.template = xml/* html */ `
+
+ `;
+
+ const root = await mount(Root, testUtils.prepareTarget());
+
+ // Check 1 popup
+ let popup1Promise = root.showPopup('CustomPopup1', {});
+ await testUtils.nextTick();
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
+ testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .confirm'));
+ let result1 = await popup1Promise;
+ assert.strictEqual(result1.confirmed, true);
+ await testUtils.nextTick();
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
+
+ // Check multiple popups
+ popup1Promise = root.showPopup('CustomPopup1', {});
+ await testUtils.nextTick();
+
+ // Check if the first popup is shown.
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
+
+ let popup2Promise = root.showPopup('CustomPopup2', {});
+ await testUtils.nextTick();
+
+ // Check for the second popup.
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
+
+ // popup 1 should be hidden
+ assert.strictEqual(root.el.querySelectorAll('.modal-dialog.oe_hidden').length, 1);
+
+ // click confirm on popup 2
+ testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-2 .confirm'));
+ await testUtils.nextTick();
+
+ // after confirming on popup 2, only 1 should remain.
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
+ assert.strictEqual(root.el.querySelectorAll('.modal-dialog .custom-popup-2').length, 0);
+
+ // popup 1 should not be hidden
+ const CustomPopup1 = root.el.querySelector('.modal-dialog')
+ assert.strictEqual(![...CustomPopup1.classList].includes('oe_hidden'), true);
+ testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .cancel'));
+ await testUtils.nextTick();
+
+ // after cancelling popup 1, no popup should remain.
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
+
+ result1 = await popup1Promise;
+ let result2 = await popup2Promise;
+ assert.strictEqual(result1.confirmed, false); // false because it's cancelled.
+ assert.strictEqual(result2.confirmed, true); // true because it's confirmed.
+ });
+
+ QUnit.test('pressing cancel/confirm key should only close the top popup', async function(assert) {
+ assert.expect(6);
+
+ class Root extends PosComponent {
+ setup() {
+ super.setup();
+ useSubEnv({
+ isDebug: () => false,
+ posbus: new EventBus(),
+ });
+ }
+ }
+ Root.env = makeTestEnvironment();
+ Root.template = xml/* html */ `
+
+ `;
+
+ const root = await mount(Root, testUtils.prepareTarget());
+
+ let popup1Promise = root.showPopup('CustomPopup1', { confirmKey: 'Enter', cancelKey: 'Escape' });
+ await testUtils.nextTick();
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
+
+ let popup2Promise = root.showPopup('CustomPopup2', { confirmKey: 'Enter', cancelKey: 'Escape' });
+ await testUtils.nextTick();
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
+
+ // Pressing 'Escape' should cancel the top popup which is the CustomPopup2.
+ testUtils.dom.triggerEvent(window, 'keyup', { key: 'Escape' });
+ await testUtils.nextTick();
+
+ // Therefore, the popup2Promise has now resolved with `confirmed` value = false.
+ const result2 = await popup2Promise;
+ assert.strictEqual(result2.confirmed, false);
+
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
+
+ testUtils.dom.triggerEvent(window, 'keyup', { key: 'Enter' });
+ await testUtils.nextTick();
+
+ assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
+
+ const result1 = await popup1Promise;
+ assert.strictEqual(result1.confirmed, true);
+ });
+});
diff --git a/addons/pos_coupon/static/src/js/ControlButtons/ResetProgramsButton.js b/addons/pos_coupon/static/src/js/ControlButtons/ResetProgramsButton.js
index 5b6b0894756..dade62630c0 100644
--- a/addons/pos_coupon/static/src/js/ControlButtons/ResetProgramsButton.js
+++ b/addons/pos_coupon/static/src/js/ControlButtons/ResetProgramsButton.js
@@ -14,7 +14,6 @@ odoo.define('pos_coupon.ResetProgramsButton', function (require) {
async onClick() {
const order = this.env.pos.get_order();
order.resetPrograms();
- this.trigger('close-popup');
}
}
ResetProgramsButton.template = 'ResetProgramsButton';
diff --git a/addons/pos_gift_card/static/src/xml/GiftCardPopup.xml b/addons/pos_gift_card/static/src/xml/GiftCardPopup.xml
index a61f3a7e0cf..aea1ffc0478 100644
--- a/addons/pos_gift_card/static/src/xml/GiftCardPopup.xml
+++ b/addons/pos_gift_card/static/src/xml/GiftCardPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml b/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml
index 39c05a8fce0..7bac9c11868 100644
--- a/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml
+++ b/addons/pos_mercury/static/src/xml/PaymentTransactionPopup.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js
index e892a479a75..7d048f0a48f 100644
--- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js
@@ -14,7 +14,6 @@ odoo.define('pos_restaurant.PrintBillButton', function(require) {
async onClick() {
const order = this.env.pos.get_order();
if (order.get_orderlines().length > 0) {
- this.trigger('close-popup');
await this.showTempScreen('BillScreen');
} else {
await this.showPopup('ErrorPopup', {
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js
index 5e5fdc79ae0..5ecd9627412 100644
--- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js
@@ -14,7 +14,6 @@ odoo.define('pos_restaurant.SplitBillButton', function(require) {
async onClick() {
const order = this.env.pos.get_order();
if (order.get_orderlines().length > 0) {
- this.trigger('close-popup');
this.showScreen('SplitBillScreen');
}
}
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js
index 67a2c4fb0dc..67211231dfb 100644
--- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js
@@ -12,7 +12,6 @@ odoo.define('pos_restaurant.TransferOrderButton', function(require) {
useListener('click', this.onClick);
}
async onClick() {
- this.trigger('close-popup');
this.env.pos.transfer_order_to_different_table();
this.showScreen('FloorScreen');
}
diff --git a/addons/pos_sale/static/src/js/SetSaleOrderButton.js b/addons/pos_sale/static/src/js/SetSaleOrderButton.js
index d0dd57ecb89..3c7ad5792ef 100644
--- a/addons/pos_sale/static/src/js/SetSaleOrderButton.js
+++ b/addons/pos_sale/static/src/js/SetSaleOrderButton.js
@@ -24,7 +24,6 @@ odoo.define('pos_sale.SetSaleOrderButton', function(require) {
args: [[]],
kwargs: { context: this.env.session.user_context },
});
- this.trigger('close-popup');
this.showScreen('SaleOrderManagementScreen');
} catch (error) {
if (isConnectionError(error)) {