From 541e3ef38f197928cf9153ee26d3f8ebc138992b Mon Sep 17 00:00:00 2001 From: Bruno Boi Date: Thu, 18 Nov 2021 13:05:06 +0000 Subject: [PATCH] [FIX] web: rainbowman effect must always use the RainbowMan component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit Calling the rainbow_man effect with a custom component would replace entirely the RainbowMan component instead of passing it a subcomponent to display. After this commit It is fixed. closes odoo/odoo#79962 Signed-off-by: Géry Debongnie (ged) --- .../static/src/core/effects/effect_service.js | 34 +++++++------- .../static/src/core/effects/rainbow_man.js | 36 +++++++++------ .../core/effects/effect_service_tests.js | 45 +++++++++++++------ 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/addons/web/static/src/core/effects/effect_service.js b/addons/web/static/src/core/effects/effect_service.js index 5f2401b7884..e789e77db1c 100644 --- a/addons/web/static/src/core/effects/effect_service.js +++ b/addons/web/static/src/core/effects/effect_service.js @@ -19,21 +19,21 @@ const effectRegistry = registry.category("effects"); * * @param {Object} env * @param {Object} [params={}] - * @param {string} [params.message="Well Done"] + * @param {string} [params.message="Well Done!"] * The message in the notice the rainbowman holds or the content of the notification if effects are disabled * Can be a simple a string * Can be a string representation of html (prefer component if you want interactions in the DOM) - * @param {boolean} [params.img_url="/web/static/img/smile.svg"] + * @param {string} [params.img_url="/web/static/img/smile.svg"] * The url of the image to display inside the rainbow - * @param {boolean} [params.messageIsHtml] + * @param {boolean} [params.messageIsHtml=false] * Set to true if the message encodes html, s.t. it will be correctly inserted into the DOM. * @param {"slow"|"medium"|"fast"|"no"} [params.fadeout="medium"] * Delay for rainbowman to disappear * 'fast' will make rainbowman dissapear quickly * 'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer) * 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman - * @param {owl.Component} [params.Component=RainbowMan] - * Component class to instantiate (if effects aren't disabled) + * @param {owl.Component} [params.Component] + * Custom Component class to instantiate inside the Rainbow Man * @param {Object} [params.props] * If params.Component is given, its props can be passed with this argument */ @@ -50,19 +50,19 @@ function rainbowMan(env, params = {}) { ); message = message.outerHTML; } else if (!message) { - message = env._t("well Done!"); + message = env._t("Well Done!"); } if (env.services.user.showEffect) { - return { - Component: params.Component || RainbowMan, - props: { - imgUrl: params.img_url || "/web/static/img/smile.svg", - fadeout: params.fadeout, - message, - messageIsHtml: params.messageIsHtml || false, - ...params.props, - }, + /** @type {import("./rainbow_man").RainbowManProps} */ + const props = { + imgUrl: params.img_url || "/web/static/img/smile.svg", + fadeout: params.fadeout || "medium", + message, + messageIsHtml: params.messageIsHtml || false, + Component: params.Component, + props: params.props, }; + return { Component: RainbowMan, props }; } env.services.notification.add(message); } @@ -82,10 +82,10 @@ export const effectService = { let effectId = 0; /** - * @param {Object} params various params depending on the type of effect + * @param {Object} [params] various params depending on the type of effect * @param {string} [params.type="rainbow_man"] the effect to display */ - function add(params) { + function add(params = {}) { const type = params.type || "rainbow_man"; const effect = effectRegistry.get(type); const { Component, props } = effect(env, params) || {}; diff --git a/addons/web/static/src/core/effects/rainbow_man.js b/addons/web/static/src/core/effects/rainbow_man.js index a8aae22ac09..f3243edb6da 100644 --- a/addons/web/static/src/core/effects/rainbow_man.js +++ b/addons/web/static/src/core/effects/rainbow_man.js @@ -5,6 +5,26 @@ import { useEffect } from "@web/core/utils/hooks"; const { Component, hooks } = owl; +/** + * @typedef Common + * @property {string} [fadeout='medium'] Delay for rainbowman to disappear. + * - 'fast' will make rainbowman dissapear quickly, + * - 'medium' and 'slow' will wait little longer before disappearing + * (can be used when props.message is longer), + * - 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman + * @property {string} [imgUrl] URL of the image to be displayed + * + * @typedef Simple + * @property {string} message Message to be displayed on rainbowman card + * @property {boolean} [messageIsHtml=false] + * + * @typedef Custom + * @property {Component} Component + * @property {any} [props] + * + * @typedef {Common & (Simple | Custom)} RainbowManProps + */ + /** * The RainbowMan widget is the widget displayed by default as a 'fun/rewarding' * effect in some cases. For example, when the user marked a large deal as won, @@ -16,20 +36,10 @@ const { Component, hooks } = owl; * service (by triggering the 'show_effect' event) */ export class RainbowMan extends Component { - /** - * @override - * @constructor - * @param {Object} [options] - * @param {string} [options.message] Message to be displayed on rainbowman card - * @param {string} [options.fadeout='medium'] Delay for rainbowman to disappear. 'fast' will make rainbowman dissapear quickly, 'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer), 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman - * @param {string} [options.img_url] URL of the image to be displayed - */ setup() { hooks.useExternalListener(document.body, "click", this.closeRainbowMan); - const fadeout = "fadeout" in this.props ? this.props.fadeout : "medium"; - const delay = fadeout ? RainbowMan.rainbowFadeouts[fadeout] : false; - this.delay = typeof delay === "number" ? delay : false; - if (this.delay !== false) { + this.delay = RainbowMan.rainbowFadeouts[this.props.fadeout]; + if (this.delay) { useEffect( () => { const timeout = browser.setTimeout(() => { @@ -43,7 +53,7 @@ export class RainbowMan extends Component { } onAnimationEnd(ev) { - if (this.delay !== false && ev.animationName === "reward-fading-reverse") { + if (this.delay && ev.animationName === "reward-fading-reverse") { ev.stopPropagation(); this.closeRainbowMan(); } diff --git a/addons/web/static/tests/core/effects/effect_service_tests.js b/addons/web/static/tests/core/effects/effect_service_tests.js index ae673191e85..c8a473e037b 100644 --- a/addons/web/static/tests/core/effects/effect_service_tests.js +++ b/addons/web/static/tests/core/effects/effect_service_tests.js @@ -3,12 +3,11 @@ import { notificationService } from "@web/core/notifications/notification_service"; import { registry } from "@web/core/registry"; import { effectService } from "@web/core/effects/effect_service"; -import { RainbowMan } from "@web/core/effects/rainbow_man"; import { userService } from "@web/core/user_service"; import { session } from "@web/session"; import { makeTestEnv } from "../../helpers/mock_env"; import { makeFakeLocalizationService } from "../../helpers/mock_services"; -import { click, getFixture, nextTick, patchWithCleanup } from "../../helpers/utils"; +import { click, getFixture, mockTimeout, nextTick, patchWithCleanup } from "../../helpers/utils"; import { registerCleanup } from "../../helpers/cleanup"; const { Component, mount, tags } = owl; @@ -38,13 +37,14 @@ async function makeParent() { QUnit.module("Effect Service", (hooks) => { let effectParams; + let execRegisteredTimeouts; hooks.beforeEach(() => { effectParams = { message: "
Congrats!
", messageIsHtml: true, - fadeout: "nextTick", }; + execRegisteredTimeouts = mockTimeout(); patchWithCleanup(session, { show_effect: true }); // enable effects serviceRegistry.add("user", userService); @@ -55,34 +55,31 @@ QUnit.module("Effect Service", (hooks) => { QUnit.test("effect service displays a rainbowman by default", async function (assert) { const parent = await makeParent(); - - parent.env.services.effect.add({ message: "Hello", fadeout: "no" }); + parent.env.services.effect.add(); await nextTick(); + execRegisteredTimeouts(); assert.containsOnce(parent.el, ".o_reward"); - assert.strictEqual(parent.el.querySelector(".o_reward").innerText, "Hello"); + assert.strictEqual(parent.el.querySelector(".o_reward").innerText, "Well Done!"); }); QUnit.test("rainbowman effect with show_effect: false", async function (assert) { patchWithCleanup(session, { show_effect: false }); const parent = await makeParent(); - - parent.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" }); + parent.env.services.effect.add(); await nextTick(); + execRegisteredTimeouts(); assert.containsNone(parent.el, ".o_reward"); assert.containsOnce(parent.el, ".o_notification"); }); QUnit.test("rendering a rainbowman destroy after animation", async function (assert) { - patchWithCleanup(RainbowMan, { - rainbowFadeouts: { nextTick: 0 }, - }); - const parent = await makeParent(); parent.env.services.effect.add(effectParams); await nextTick(); + execRegisteredTimeouts(); assert.containsOnce(parent, ".o_reward"); assert.containsOnce(parent, ".o_reward_rainbow"); @@ -100,8 +97,9 @@ QUnit.module("Effect Service", (hooks) => { QUnit.test("rendering a rainbowman destroy on click", async function (assert) { const parent = await makeParent(); - parent.env.services.effect.add({ ...effectParams, fadeout: "no" }); + parent.env.services.effect.add(effectParams); await nextTick(); + execRegisteredTimeouts(); assert.containsOnce(parent.el, ".o_reward"); assert.containsOnce(parent.el, ".o_reward_rainbow"); @@ -115,6 +113,7 @@ QUnit.module("Effect Service", (hooks) => { parent.env.services.effect.add({ ...effectParams, messageIsHtml: false }); await nextTick(); + execRegisteredTimeouts(); assert.containsOnce(parent.el, ".o_reward"); assert.containsOnce(parent.el, ".o_reward_rainbow"); @@ -123,4 +122,24 @@ QUnit.module("Effect Service", (hooks) => { "
Congrats!
" ); }); + + QUnit.test("rendering a rainbowman with a custom component", async function (assert) { + assert.expect(2); + const props = { foo: "bar" }; + class Custom extends Component { + setup() { + assert.deepEqual(this.props, props, "should have received these props"); + } + } + Custom.template = tags.xml`
foo is
`; + + const parent = await makeParent(); + parent.env.services.effect.add({ Component: Custom, props }); + await nextTick(); + execRegisteredTimeouts(); + assert.strictEqual( + parent.el.querySelector(".o_reward_msg_content").innerHTML, + `
foo is bar
` + ); + }); });