[FIX] web: effect: display rainbowman by default
In [1], we rewrote the effect feature in the wowl infrastructure.
However, we lost something in the process: the "type" of effect
(basically "rainbow_man" as it is the only implemented effect)
became a mandatory param, whereas before it simply defaulted to
"rainbow_man" when not set. As a consequence, previously defined
effects in views could crash.
This commit fixes that issue, and reworks a bit the feature by
introducing a registry of effects, which easily allows to add new
effects from the outside. An effect is a function that may return
a Component (and its props) to display.
Most of the diff in the tests is about moving files where they
should be (the effect feature being in core/, the tests must be
in core/ as well, and not depend on the webclient).
[1] 0573acae23
Task 2652927
closes odoo/odoo#77109
X-original-commit: 74dc3f654ee5849982b494e46eefb1fc2b4e0c74
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
@@ -1,25 +1,24 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { RainbowMan } from "./rainbow_man";
|
||||
|
||||
const { Component, tags } = owl;
|
||||
|
||||
export class EffectContainer extends Component {
|
||||
setup() {
|
||||
this.rainbowProps = {};
|
||||
this.effect = null;
|
||||
this.props.bus.on("UPDATE", this, (effect) => {
|
||||
this.rainbowProps = effect;
|
||||
this.effect = effect;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
closeRainbowMan() {
|
||||
this.rainbowProps = {};
|
||||
removeEffect() {
|
||||
this.effect = null;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
EffectContainer.template = tags.xml`
|
||||
<div class="o_effects_manager">
|
||||
<RainbowMan t-if="rainbowProps.id" t-props="rainbowProps" t-key="rainbowProps.id" t-on-close-rainbowman="closeRainbowMan"/>
|
||||
<t t-if="effect">
|
||||
<t t-component="effect.Component" t-props="effect.props" t-key="effect.id" close="() => removeEffect()"/>
|
||||
</t>
|
||||
</div>`;
|
||||
EffectContainer.components = { RainbowMan };
|
||||
|
||||
@@ -2,28 +2,78 @@
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { EffectContainer } from "./effect_container";
|
||||
import { RainbowMan } from "./rainbow_man";
|
||||
|
||||
const { EventBus } = owl.core;
|
||||
|
||||
export function convertRainBowMessage(message) {
|
||||
const effectRegistry = registry.category("effects");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RainbowMan effect
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handles effect of type "rainbow_man". If the effects aren't disabled, returns
|
||||
* the RainbowMan component to instantiate and its props. If the effects are
|
||||
* disabled, displays the message in a notification.
|
||||
*
|
||||
* @param {Object} env
|
||||
* @param {Object} [params={}]
|
||||
* @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"]
|
||||
* The url of the image to display inside the rainbow
|
||||
* @param {boolean} [params.messageIsHtml]
|
||||
* 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 {Object} [params.props]
|
||||
* If params.Component is given, its props can be passed with this argument
|
||||
*/
|
||||
function rainbowMan(env, params = {}) {
|
||||
let message = params.message;
|
||||
if (message instanceof jQuery) {
|
||||
console.log(
|
||||
"Providing jQuery to an effect is deprecated. Note that all event handlers will be lost."
|
||||
"Providing a jQuery element to an effect is deprecated. Note that all event handlers will be lost."
|
||||
);
|
||||
return message.html();
|
||||
message = message.html();
|
||||
} else if (message instanceof Element) {
|
||||
console.log(
|
||||
"Providing HTML to an effect is deprecated. Note that all event handlers will be lost."
|
||||
"Providing an HTML element to an effect is deprecated. Note that all event handlers will be lost."
|
||||
);
|
||||
return message.outerHTML;
|
||||
} else if (typeof message === "string") {
|
||||
return message;
|
||||
message = message.outerHTML;
|
||||
} else if (!message) {
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
env.services.notification.add(message);
|
||||
}
|
||||
effectRegistry.add("rainbow_man", rainbowMan);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Effect service
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const effectService = {
|
||||
dependencies: ["notification", "user"],
|
||||
start(env, { notification, user }) {
|
||||
start(env) {
|
||||
const bus = new EventBus();
|
||||
registry.category("main_components").add("EffectContainer", {
|
||||
Component: EffectContainer,
|
||||
@@ -32,74 +82,15 @@ export const effectService = {
|
||||
let effectId = 0;
|
||||
|
||||
/**
|
||||
* This private method checks if the effects are disabled.
|
||||
* If so, it makes a notification from the message attribute of an effect and
|
||||
* doesn't trigger the effect at all.
|
||||
* @param {Object} effect The effect to display
|
||||
* @param {Object} params various params depending on the type of effect
|
||||
* @param {string} [params.type="rainbow_man"] the effect to display
|
||||
*/
|
||||
function applyEffect(effect) {
|
||||
if (!user.showEffect) {
|
||||
notification.add(effect.message, { sticky: false });
|
||||
} else {
|
||||
bus.trigger("UPDATE", effect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a rainbowman effect
|
||||
*
|
||||
* @param {Object} [params={}]
|
||||
* @param {string|Function} [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)
|
||||
* Can be a function returning a string (like _t)
|
||||
* @param {boolean} [params.messageIsHtml]
|
||||
* The message can be a string representation of html but it needs to be marked as well
|
||||
* @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]
|
||||
* Component class to instantiate
|
||||
* It this option is set, the message option is ignored unless effect are disbled
|
||||
* Then, the message param is used to display a notification
|
||||
* @param {Object} [params.props]
|
||||
* If a component is used, its props can be passed with this argument
|
||||
*/
|
||||
function rainbowMan(params = {}) {
|
||||
const effect = Object.assign({
|
||||
imgUrl: "/web/static/img/smile.svg",
|
||||
fadeout: params.fadeout,
|
||||
id: ++effectId,
|
||||
message: convertRainBowMessage(params.message) || env._t("Well Done!"),
|
||||
messageIsHtml: params.messageIsHtml || false,
|
||||
Component: params.Component,
|
||||
props: params.props,
|
||||
});
|
||||
applyEffect(effect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an effect
|
||||
* This is a dispatcher for the effect. Usefull if the request for effect comes
|
||||
* from the server.
|
||||
* In the weblient, use the more specific effect functions.
|
||||
*
|
||||
* @param {string} type
|
||||
* What effect to create
|
||||
* - rainbowman
|
||||
* @param {Object} [params={}]
|
||||
* All the options for the effect.
|
||||
* The options get passed to the more specific effect methods.
|
||||
*/
|
||||
function add(type, params = {}) {
|
||||
switch (type.replace("_", "").toLowerCase()) {
|
||||
case "rainbowman":
|
||||
return rainbowMan(params);
|
||||
default:
|
||||
throw new Error("NON_IMPLEMENTED_EFFECT");
|
||||
function add(params) {
|
||||
const type = params.type || "rainbow_man";
|
||||
const effect = effectRegistry.get(type);
|
||||
const { Component, props } = effect(env, params) || {};
|
||||
if (Component) {
|
||||
bus.trigger("UPDATE", { Component, props, id: effectId++ });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export class RainbowMan extends Component {
|
||||
}
|
||||
|
||||
closeRainbowMan() {
|
||||
this.trigger("close-rainbowman");
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
RainbowMan.template = "web.RainbowMan";
|
||||
|
||||
@@ -11,10 +11,10 @@ export const legacyServiceProvider = {
|
||||
dependencies: ["effect", "action"],
|
||||
start({ services }) {
|
||||
browser.addEventListener("show-effect", (ev) => {
|
||||
services.effect.add(ev.detail.type, ev.detail);
|
||||
services.effect.add(ev.detail);
|
||||
});
|
||||
bus.on("show-effect", this, (payload) => {
|
||||
services.effect.add(payload.type, payload);
|
||||
services.effect.add(payload);
|
||||
});
|
||||
|
||||
browser.addEventListener("do-action", (ev) => {
|
||||
|
||||
@@ -296,7 +296,7 @@ export function makeLegacyRainbowManService(legacyEnv) {
|
||||
dependencies: ["effect"],
|
||||
start(env, { effect }) {
|
||||
legacyEnv.bus.on("show-effect", null, (payload) => {
|
||||
effect.add(payload.type, payload);
|
||||
effect.add(payload);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1153,7 +1153,7 @@ function makeActionManager(env) {
|
||||
await _executeCloseAction();
|
||||
}
|
||||
if (effect) {
|
||||
env.services.effect.add(effect.type, effect);
|
||||
env.services.effect.add(effect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
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 { registerCleanup } from "../../helpers/cleanup";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
const serviceRegistry = registry.category("services");
|
||||
const mainComponentRegistry = registry.category("main_components");
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.EffectContainer = mainComponentRegistry.get("EffectContainer");
|
||||
this.NotificationContainer = mainComponentRegistry.get("NotificationContainer");
|
||||
}
|
||||
}
|
||||
Parent.template = tags.xml`
|
||||
<div>
|
||||
<t t-component="EffectContainer.Component" t-props="EffectContainer.props" />
|
||||
<t t-component="NotificationContainer.Component" t-props="NotificationContainer.props" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
async function makeParent() {
|
||||
const env = await makeTestEnv({ serviceRegistry });
|
||||
const target = getFixture();
|
||||
const parent = await mount(Parent, { env, target });
|
||||
registerCleanup(() => parent.destroy());
|
||||
return parent;
|
||||
}
|
||||
|
||||
QUnit.module("Effect Service", (hooks) => {
|
||||
let effectParams;
|
||||
hooks.beforeEach(() => {
|
||||
effectParams = {
|
||||
message: "<div>Congrats!</div>",
|
||||
messageIsHtml: true,
|
||||
fadeout: "nextTick",
|
||||
};
|
||||
|
||||
patchWithCleanup(session, { show_effect: true }); // enable effects
|
||||
|
||||
serviceRegistry.add("user", userService);
|
||||
serviceRegistry.add("effect", effectService);
|
||||
serviceRegistry.add("notification", notificationService);
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService());
|
||||
});
|
||||
|
||||
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" });
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(parent.el, ".o_reward");
|
||||
assert.strictEqual(parent.el.querySelector(".o_reward").innerText, "Hello");
|
||||
});
|
||||
|
||||
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" });
|
||||
await nextTick();
|
||||
|
||||
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();
|
||||
|
||||
assert.containsOnce(parent, ".o_reward");
|
||||
assert.containsOnce(parent, ".o_reward_rainbow");
|
||||
assert.strictEqual(
|
||||
parent.el.querySelector(".o_reward_msg_content").innerHTML,
|
||||
"<div>Congrats!</div>"
|
||||
);
|
||||
|
||||
const ev = new AnimationEvent("animationend", { animationName: "reward-fading-reverse" });
|
||||
parent.el.querySelector(".o_reward").dispatchEvent(ev);
|
||||
await nextTick();
|
||||
assert.containsNone(parent, ".o_reward");
|
||||
});
|
||||
|
||||
QUnit.test("rendering a rainbowman destroy on click", async function (assert) {
|
||||
const parent = await makeParent();
|
||||
|
||||
parent.env.services.effect.add({ ...effectParams, fadeout: "no" });
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(parent.el, ".o_reward");
|
||||
assert.containsOnce(parent.el, ".o_reward_rainbow");
|
||||
|
||||
await click(parent.el);
|
||||
assert.containsNone(parent, ".o_reward");
|
||||
});
|
||||
|
||||
QUnit.test("rendering a rainbowman with an escaped message", async function (assert) {
|
||||
const parent = await makeParent();
|
||||
|
||||
parent.env.services.effect.add({ ...effectParams, messageIsHtml: false });
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(parent.el, ".o_reward");
|
||||
assert.containsOnce(parent.el, ".o_reward_rainbow");
|
||||
assert.strictEqual(
|
||||
parent.el.querySelector(".o_reward_msg_content").textContent,
|
||||
"<div>Congrats!</div>"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await doAction(webClient, 1);
|
||||
assert.containsOnce(webClient.el, ".o_kanban_view");
|
||||
assert.containsNone(webClient.el, ".o_reward");
|
||||
webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
|
||||
webClient.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" });
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(webClient.el, ".o_reward");
|
||||
@@ -36,7 +36,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
await legacyExtraNextTick();
|
||||
assert.containsNone(webClient.el, ".o_reward");
|
||||
assert.containsOnce(webClient.el, ".o_kanban_view");
|
||||
webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
|
||||
webClient.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" });
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(webClient.el, ".o_reward");
|
||||
@@ -48,22 +48,6 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
assert.containsOnce(webClient.el, ".o_list_view");
|
||||
});
|
||||
|
||||
QUnit.test("show effect notification instead of rainbow man", async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
await doAction(webClient, 1);
|
||||
assert.containsOnce(webClient.el, ".o_kanban_view");
|
||||
assert.containsNone(webClient.el, ".o_reward");
|
||||
assert.containsNone(webClient.el, ".o_notification");
|
||||
webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
|
||||
await nextTick();
|
||||
await legacyExtraNextTick();
|
||||
assert.containsOnce(webClient.el, ".o_kanban_view");
|
||||
assert.containsNone(webClient.el, ".o_reward");
|
||||
assert.containsOnce(webClient.el, ".o_notification");
|
||||
});
|
||||
|
||||
QUnit.test("on close with effect from server", async function (assert) {
|
||||
assert.expect(1);
|
||||
patchWithCleanup(session, { show_effect: true });
|
||||
@@ -89,14 +73,14 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
QUnit.test("on close with effect in xml", async function (assert) {
|
||||
assert.expect(2);
|
||||
serverData.views["partner,false,form"] = `
|
||||
<form>
|
||||
<header>
|
||||
<button string="Call method" name="object" type="object"
|
||||
effect="{'type': 'rainbow_man', 'message': 'rainBowInXML'}"
|
||||
/>
|
||||
</header>
|
||||
<field name="display_name"/>
|
||||
</form>`;
|
||||
<form>
|
||||
<header>
|
||||
<button string="Call method" name="object" type="object"
|
||||
effect="{'type': 'rainbow_man', 'message': 'rainBowInXML'}"
|
||||
/>
|
||||
</header>
|
||||
<field name="display_name"/>
|
||||
</form>`;
|
||||
patchWithCleanup(session, { show_effect: true });
|
||||
const mockRPC = async (route) => {
|
||||
if (route === "/web/dataset/call_button") {
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
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 { makeTestEnv } from "../../helpers/mock_env";
|
||||
import { click, getFixture, nextTick, patchWithCleanup } from "../../helpers/utils";
|
||||
import { session } from "@web/session";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
class Parent extends Component {
|
||||
setup() {
|
||||
this.RainbowMgr = registry.category("main_components").get("EffectContainer");
|
||||
}
|
||||
}
|
||||
Parent.template = tags.xml`
|
||||
<div>
|
||||
<t t-component="RainbowMgr.Component" t-props="RainbowMgr.props" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
QUnit.module("RainbowMan", (hooks) => {
|
||||
let rainbowManDefault, target;
|
||||
hooks.beforeEach(async () => {
|
||||
rainbowManDefault = {
|
||||
message: "<div>Congrats!</div>",
|
||||
messageIsHtml: true,
|
||||
fadeout: "nextTick",
|
||||
};
|
||||
target = getFixture();
|
||||
patchWithCleanup(session, { show_effect: true });
|
||||
serviceRegistry.add("user", userService);
|
||||
serviceRegistry.add("effect", effectService);
|
||||
serviceRegistry.add("notification", notificationService);
|
||||
});
|
||||
|
||||
QUnit.test("rendering a rainbowman destroy after animation", async function (assert) {
|
||||
assert.expect(4);
|
||||
const _delays = RainbowMan.rainbowFadeouts;
|
||||
RainbowMan.rainbowFadeouts = { nextTick: 0 };
|
||||
const env = await makeTestEnv({ serviceRegistry });
|
||||
const parent = await mount(Parent, { env, target });
|
||||
env.services.effect.add("rainbowman", rainbowManDefault);
|
||||
await nextTick();
|
||||
assert.containsOnce(target, ".o_reward");
|
||||
assert.containsOnce(parent.el, ".o_reward_rainbow");
|
||||
assert.strictEqual(
|
||||
parent.el.querySelector(".o_reward_msg_content").innerHTML,
|
||||
"<div>Congrats!</div>"
|
||||
);
|
||||
|
||||
const ev = new AnimationEvent("animationend", { animationName: "reward-fading-reverse" });
|
||||
target.querySelector(".o_reward").dispatchEvent(ev);
|
||||
await nextTick();
|
||||
assert.containsNone(target, ".o_reward");
|
||||
RainbowMan.rainbowFadeouts = _delays;
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("rendering a rainbowman destroy on click", async function (assert) {
|
||||
assert.expect(3);
|
||||
rainbowManDefault.fadeout = "no";
|
||||
const env = await makeTestEnv({ serviceRegistry });
|
||||
const parent = await mount(Parent, { env, target });
|
||||
env.services.effect.add("rainbowman", rainbowManDefault);
|
||||
await nextTick();
|
||||
assert.containsOnce(parent.el, ".o_reward");
|
||||
assert.containsOnce(parent.el, ".o_reward_rainbow");
|
||||
await click(target);
|
||||
assert.containsNone(target, ".o_reward");
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("rendering a rainbowman with an escaped message", async function (assert) {
|
||||
assert.expect(3);
|
||||
const env = await makeTestEnv({ serviceRegistry });
|
||||
const parent = await mount(Parent, { env, target });
|
||||
env.services.effect.add("rainbowman", { ...rainbowManDefault, messageIsHtml: false });
|
||||
await nextTick();
|
||||
assert.containsOnce(parent.el, ".o_reward");
|
||||
assert.containsOnce(parent.el, ".o_reward_rainbow");
|
||||
assert.strictEqual(
|
||||
parent.el.querySelector(".o_reward_msg_content").textContent,
|
||||
"<div>Congrats!</div>"
|
||||
);
|
||||
parent.destroy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user