diff --git a/addons/web/static/src/legacy/js/core/service_mixins.js b/addons/web/static/src/legacy/js/core/service_mixins.js index de05b0843de..e35f0695ff0 100644 --- a/addons/web/static/src/legacy/js/core/service_mixins.js +++ b/addons/web/static/src/legacy/js/core/service_mixins.js @@ -230,7 +230,10 @@ var ServicesMixin = { action: action, options: options, on_success: resolve, - on_fail: reject, + on_fail: (reason) => { + reject(reason); + return "alreadyThrown" + }, }); }); }, diff --git a/addons/web/static/src/legacy/utils.js b/addons/web/static/src/legacy/utils.js index 75b0c330305..72ee1334671 100644 --- a/addons/web/static/src/legacy/utils.js +++ b/addons/web/static/src/legacy/utils.js @@ -282,10 +282,11 @@ export function makeLegacyCrashManagerService(legacyEnv) { export function wrapSuccessOrFail(promise, { on_success, on_fail } = {}) { return promise.then(on_success || (() => {})).catch((reason) => { + let alreadyThrown = false; if (on_fail) { - on_fail(reason); + alreadyThrown = on_fail(reason) === "alreadyThrown"; } - if (reason instanceof Error) { + if (reason instanceof Error && !alreadyThrown) { throw reason; } }); diff --git a/addons/web/static/tests/legacy/core/mixins_tests.js b/addons/web/static/tests/legacy/core/mixins_tests.js index 8c71a2592ce..c1c2dc80941 100644 --- a/addons/web/static/tests/legacy/core/mixins_tests.js +++ b/addons/web/static/tests/legacy/core/mixins_tests.js @@ -1,9 +1,17 @@ odoo.define('web.mixins_tests', function (require) { "use strict"; +const AbstractAction = require("web.AbstractAction"); +const core = require("web.core"); var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); +const { dialogService } = require('@web/core/dialog/dialog_service'); +const { errorService } = require("@web/core/errors/error_service"); +const { registry } = require('@web/core/registry'); +const { nextTick, patchWithCleanup } = require('@web/../tests/helpers/utils'); +const { createWebClient, doAction } = require('@web/../tests/webclient/helpers'); + QUnit.module('core', {}, function () { QUnit.module('mixins'); @@ -29,7 +37,40 @@ QUnit.module('core', {}, function () { }); }); + QUnit.test('checks that the error generated by a do_action opens one dialog', async function (assert) { + assert.expect(1); + window.addEventListener("unhandledrejection", async (ev) => { + ev.preventDefault(); + }); + patchWithCleanup(QUnit, { + onUnhandledRejection: () => {}, + }); + + const serviceRegistry = registry.category("services"); + serviceRegistry.add("dialog", dialogService); + serviceRegistry.add("error", errorService); + + const TestAction = AbstractAction.extend({ + on_attach_callback() { + this.do_action({ + id: 1, + type: "ir.actions.server", + }) + }, + }); + core.action_registry.add("TestAction", TestAction); + + const mockRPC = (route) => { + if (route === "/web/action/run") { + throw new Error("This error should be throw only once"); + } + }; + const webClient = await createWebClient({mockRPC}); + await doAction(webClient, "TestAction"); + await nextTick(); + assert.containsOnce(webClient, ".o_dialog"); + }); }); });