[FIX] web: rainbowman effect must always use the RainbowMan component

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) <ged@openerp.com>
This commit is contained in:
Bruno Boi
2021-11-19 12:07:54 +00:00
parent 74ddcdf374
commit 541e3ef38f
3 changed files with 72 additions and 43 deletions
@@ -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) || {};
@@ -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();
}
@@ -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: "<div>Congrats!</div>",
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) => {
"<div>Congrats!</div>"
);
});
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`<div class="custom">foo is <t t-esc="props.foo"/></div>`;
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,
`<div class="custom">foo is bar</div>`
);
});
});