Files
odoo_source/addons/web/static/tests/webclient/actions/target_tests.js
T
Aaron Bohy ff6e89052d [REF] web,*: regroup custom hooks in core/utils/hooks
*base_automation,iap,im_livechat,point_of_sale

This commit introduces a new file in core/utils: hooks.js
This file contains custom hooks (useEffect, useService, useBus...).

The useHotkey hook is tightly related to hotkeys, so it has been
moved to core/hotkeys.

closes odoo/odoo#74535

Related: odoo/enterprise#20003
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
2021-08-02 11:13:52 +00:00

533 lines
21 KiB
JavaScript

/** @odoo-module **/
import testUtils from "web.test_utils";
import core from "web.core";
import AbstractAction from "web.AbstractAction";
import { registry } from "@web/core/registry";
import { click, legacyExtraNextTick, patchWithCleanup, makeDeferred } from "../../helpers/utils";
import { createWebClient, doAction, getActionManagerServerData } from "./../helpers";
import { registerCleanup } from "../../helpers/cleanup";
import { errorService } from "@web/core/errors/error_service";
import { useService } from "@web/core/utils/hooks";
import { ClientErrorDialog } from "@web/core/errors/error_dialogs";
let serverData;
QUnit.module("ActionManager", (hooks) => {
hooks.beforeEach(() => {
serverData = getActionManagerServerData();
});
QUnit.module('Actions in target="new"');
QUnit.test('can execute act_window actions in target="new"', async function (assert) {
assert.expect(8);
const mockRPC = async (route, args) => {
assert.step((args && args.method) || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 5);
assert.containsOnce(
document.body,
".o_technical_modal .o_form_view",
"should have rendered a form view in a modal"
);
assert.hasClass(
$(".o_technical_modal .modal-body")[0],
"o_act_window",
"dialog main element should have classname 'o_act_window'"
);
assert.hasClass(
$(".o_technical_modal .o_form_view")[0],
"o_form_editable",
"form view should be in edit mode"
);
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"onchange",
]);
});
QUnit.test("chained action on_close", async function (assert) {
assert.expect(4);
function onClose(closeInfo) {
assert.strictEqual(closeInfo, "smallCandle");
assert.step("Close Action");
}
const webClient = await createWebClient({ serverData });
await doAction(webClient, 5, { onClose });
// a target=new action shouldn't activate the on_close
await doAction(webClient, 5);
assert.verifySteps([]);
// An act_window_close should trigger the on_close
await doAction(webClient, { type: "ir.actions.act_window_close", infos: "smallCandle" });
assert.verifySteps(["Close Action"]);
});
QUnit.test("footer buttons are moved to the dialog footer", async function (assert) {
assert.expect(3);
serverData.views["partner,false,form"] = `
<form>
<field name="display_name"/>
<footer>
<button string="Create" type="object" class="infooter"/>
</footer>
</form>`;
const webClient = await createWebClient({ serverData });
await doAction(webClient, 5);
assert.containsNone(
$(".o_technical_modal .modal-body")[0],
"button.infooter",
"the button should not be in the body"
);
assert.containsOnce(
$(".o_technical_modal .modal-footer")[0],
"button.infooter",
"the button should be in the footer"
);
assert.containsOnce(
$(".o_technical_modal .modal-footer")[0],
"button",
"the modal footer should only contain one button"
);
});
QUnit.test("Button with `close` attribute closes dialog", async function (assert) {
assert.expect(19);
serverData.views = {
"partner,false,form": `
<form>
<header>
<button string="Open dialog" name="5" type="action"/>
</header>
</form>
`,
"partner,view_ref,form": `
<form>
<footer>
<button string="I close the dialog" name="some_method" type="object" close="1"/>
</footer>
</form>
`,
"partner,false,search": "<search></search>",
};
serverData.actions[4] = {
id: 4,
name: "Partners Action 4",
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "form"]],
};
serverData.actions[5] = {
id: 5,
name: "Create a Partner",
res_model: "partner",
target: "new",
type: "ir.actions.act_window",
views: [["view_ref", "form"]],
};
const mockRPC = async (route, args) => {
assert.step(route);
if (route === "/web/dataset/call_button" && args.method === "some_method") {
return {
tag: "display_notification",
type: "ir.actions.client",
};
}
};
const webClient = await createWebClient({ serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await doAction(webClient, 4);
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/onchange",
]);
await testUtils.dom.click(`button[name="5"]`);
assert.verifySteps([
"/web/dataset/call_kw/partner/create",
"/web/dataset/call_kw/partner/read",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/onchange",
]);
await legacyExtraNextTick();
assert.strictEqual($(".modal").length, 1, "It should display a modal");
await testUtils.dom.click(`button[name="some_method"]`);
assert.verifySteps([
"/web/dataset/call_kw/partner/create",
"/web/dataset/call_kw/partner/read",
"/web/dataset/call_button",
"/web/dataset/call_kw/partner/read",
]);
await legacyExtraNextTick();
assert.strictEqual($(".modal").length, 0, "It should have closed the modal");
});
QUnit.test('on_attach_callback is called for actions in target="new"', async function (assert) {
assert.expect(3);
const ClientAction = AbstractAction.extend({
on_attach_callback: function () {
assert.step("on_attach_callback");
assert.containsOnce(
document.body,
".modal .o_test",
"should have rendered the client action in a dialog"
);
},
start: function () {
this.$el.addClass("o_test");
},
});
core.action_registry.add("test", ClientAction);
const webClient = await createWebClient({ serverData });
await doAction(webClient, {
tag: "test",
target: "new",
type: "ir.actions.client",
});
assert.verifySteps(["on_attach_callback"]);
delete core.action_registry.map.test;
});
QUnit.test(
'footer buttons are updated when having another action in target "new"',
async function (assert) {
assert.expect(9);
serverData.views["partner,false,form"] =
"<form>" +
'<field name="display_name"/>' +
"<footer>" +
'<button string="Create" type="object" class="infooter"/>' +
"</footer>" +
"</form>";
const webClient = await createWebClient({ serverData });
await doAction(webClient, 5);
assert.containsNone(
webClient.el,
'.o_technical_modal .modal-body button[special="save"]'
);
assert.containsNone(webClient.el, ".o_technical_modal .modal-body button.infooter");
assert.containsOnce(webClient.el, ".o_technical_modal .modal-footer button.infooter");
assert.containsOnce(webClient.el, ".o_technical_modal .modal-footer button");
await doAction(webClient, 25);
assert.containsNone(webClient.el, ".o_technical_modal .modal-body button.infooter");
assert.containsNone(webClient.el, ".o_technical_modal .modal-footer button.infooter");
assert.containsNone(
webClient.el,
'.o_technical_modal .modal-body button[special="save"]'
);
assert.containsOnce(
webClient.el,
'.o_technical_modal .modal-footer button[special="save"]'
);
assert.containsOnce(webClient.el, ".o_technical_modal .modal-footer button");
}
);
QUnit.test(
'buttons of client action in target="new" and transition to MVC action',
async function (assert) {
assert.expect(4);
const ClientAction = AbstractAction.extend({
renderButtons($target) {
const button = document.createElement("button");
button.setAttribute("class", "o_stagger_lee");
$target[0].appendChild(button);
},
});
core.action_registry.add("test", ClientAction);
const webClient = await createWebClient({ serverData });
await doAction(webClient, {
tag: "test",
target: "new",
type: "ir.actions.client",
});
assert.containsOnce(webClient.el, ".modal footer button.o_stagger_lee");
assert.containsNone(webClient.el, '.modal footer button[special="save"]');
await doAction(webClient, 25);
assert.containsNone(webClient.el, ".modal footer button.o_stagger_lee");
assert.containsOnce(webClient.el, '.modal footer button[special="save"]');
delete core.action_registry.map.test;
}
);
QUnit.test(
'button with confirm attribute in act_window action in target="new"',
async function (assert) {
assert.expect(5);
serverData.actions[999] = {
id: 999,
name: "A window action",
res_model: "partner",
target: "new",
type: "ir.actions.act_window",
views: [[999, "form"]],
};
serverData.views["partner,999,form"] = `
<form>
<button name="method" string="Call method" type="object" confirm="Are you sure?"/>
</form>`;
serverData.views["partner,1000,form"] = `<form>Another action</form>`;
const mockRPC = (route, args) => {
if (args.method === "method") {
return Promise.resolve({
id: 1000,
name: "Another window action",
res_model: "partner",
target: "new",
type: "ir.actions.act_window",
views: [[1000, "form"]],
});
}
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 999);
assert.containsOnce(document.body, ".modal button[name=method]");
await testUtils.dom.click($(".modal button[name=method]"));
assert.containsN(document.body, ".modal", 2);
assert.strictEqual($(".modal:last .modal-body").text(), "Are you sure?");
await testUtils.dom.click($(".modal:last .modal-footer .btn-primary"));
assert.containsOnce(document.body, ".modal");
assert.strictEqual($(".modal:last .modal-body").text().trim(), "Another action");
}
);
QUnit.test('actions in target="new" do not update page title', async function (assert) {
const mockedTitleService = {
start() {
return {
setParts({ action }) {
if (action) {
assert.step(action);
}
},
};
},
};
registry.category("services").add("title", mockedTitleService);
const webClient = await createWebClient({ serverData });
// sanity check: execute an action in target="current"
await doAction(webClient, 1);
assert.verifySteps(["Partners Action 1"]);
// execute an action in target="new"
await doAction(webClient, 5);
assert.verifySteps([]);
});
QUnit.test("do not commit a dialog in error", async (assert) => {
assert.expect(6);
const handler = (ev) => {
// need to preventDefault to remove error from console (so python test pass)
ev.preventDefault();
};
window.addEventListener("unhandledrejection", handler);
registerCleanup(() => window.removeEventListener("unhandledrejection", handler));
patchWithCleanup(QUnit, {
onUnhandledRejection: () => {},
});
class ErrorClientAction extends owl.Component {
setup() {
throw new Error("my error");
}
}
ErrorClientAction.template = owl.tags.xml`<div/>`;
registry.category("actions").add("failing", ErrorClientAction);
class ClientActionTargetNew extends owl.Component {}
ClientActionTargetNew.template = owl.tags.xml`<div class="my_action_new" />`;
registry.category("actions").add("clientActionNew", ClientActionTargetNew);
class ClientAction extends owl.Component {
setup() {
this.action = useService("action");
}
async onClick() {
try {
await this.action.doAction(
{ type: "ir.actions.client", tag: "failing", target: "new" },
{ onClose: () => assert.step("failing dialog closed") }
);
} catch (e) {
assert.strictEqual(e.message, "my error");
}
}
}
ClientAction.template = owl.tags.xml`<div class="my_action" t-on-click="onClick" />`;
registry.category("actions").add("clientAction", ClientAction);
const errorDialogOpened = makeDeferred();
patchWithCleanup(ClientErrorDialog.prototype, {
mounted() {
this._super(...arguments);
errorDialogOpened.resolve();
},
});
registry.category("services").add("error", errorService);
const webClient = await createWebClient({});
await doAction(webClient, { type: "ir.actions.client", tag: "clientAction" });
await click(webClient.el, ".my_action");
await errorDialogOpened;
assert.containsOnce(webClient, ".modal");
await click(webClient.el, ".modal-body button.btn-link");
assert.ok(
webClient.el
.querySelector(".modal-body .o_error_detail")
.textContent.includes("my error")
);
await click(webClient.el, ".modal-footer button");
assert.containsNone(webClient, ".modal");
await doAction(webClient, {
type: "ir.actions.client",
tag: "clientActionNew",
target: "new",
});
assert.containsOnce(webClient, ".modal .my_action_new");
assert.verifySteps([]);
});
QUnit.module('Actions in target="inline"');
QUnit.test(
'form views for actions in target="inline" open in edit mode',
async function (assert) {
assert.expect(6);
const mockRPC = async (route, args) => {
assert.step(args.method || route);
};
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 6);
assert.containsOnce(
webClient,
".o_form_view.o_form_editable",
"should have rendered a form view in edit mode"
);
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"read",
]);
}
);
QUnit.test("breadcrumbs and actions with target inline", async function (assert) {
assert.expect(4);
serverData.actions[4].views = [[false, "form"]];
serverData.actions[4].target = "inline";
const webClient = await createWebClient({ serverData });
await doAction(webClient, 4);
assert.containsNone(webClient, ".o_control_panel");
await doAction(webClient, 1, { clearBreadcrumbs: true });
assert.containsOnce(webClient, ".o_control_panel");
assert.isVisible(webClient.el.querySelector(".o_control_panel"));
assert.strictEqual(
webClient.el.querySelector(".o_control_panel .breadcrumb").textContent,
"Partners Action 1",
"should have only one current action visible in breadcrumbs"
);
});
QUnit.module('Actions in target="fullscreen"');
QUnit.test(
'correctly execute act_window actions in target="fullscreen"',
async function (assert) {
assert.expect(3);
serverData.actions[1].target = "fullscreen";
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
assert.containsOnce(
webClient.el,
".o_control_panel",
"should have rendered a control panel"
);
assert.containsOnce(webClient, ".o_kanban_view", "should have rendered a kanban view");
assert.isNotVisible(webClient.el.querySelector(".o_main_navbar"));
}
);
QUnit.test('fullscreen on action change: back to a "current" action', async function (assert) {
assert.expect(3);
serverData.actions[1].target = "fullscreen";
serverData.views[
"partner,false,form"
] = `<form><button name="1" type="action" class="oe_stat_button" /></form>`;
const webClient = await createWebClient({ serverData });
await doAction(webClient, 6);
assert.isVisible(webClient.el.querySelector(".o_main_navbar"));
await testUtils.dom.click($(webClient.el).find("button[name=1]"));
await legacyExtraNextTick();
assert.isNotVisible(webClient.el.querySelector(".o_main_navbar"));
await testUtils.dom.click($(webClient.el).find(".breadcrumb li a:first"));
await legacyExtraNextTick();
assert.isVisible(webClient.el.querySelector(".o_main_navbar"));
});
QUnit.test('fullscreen on action change: all "fullscreen" actions', async function (assert) {
assert.expect(3);
serverData.actions[6].target = "fullscreen";
serverData.views[
"partner,false,form"
] = `<form><button name="1" type="action" class="oe_stat_button" /></form>`;
const webClient = await createWebClient({ serverData });
await doAction(webClient, 6);
assert.isNotVisible(webClient.el.querySelector(".o_main_navbar"));
await testUtils.dom.click($(webClient.el).find("button[name=1]"));
await legacyExtraNextTick();
assert.isNotVisible(webClient.el.querySelector(".o_main_navbar"));
await testUtils.dom.click($(webClient.el).find(".breadcrumb li a:first"));
await legacyExtraNextTick();
assert.isNotVisible(webClient.el.querySelector(".o_main_navbar"));
});
QUnit.test(
'fullscreen on action change: back to another "current" action',
async function (assert) {
assert.expect(8);
serverData.menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: { id: 1, children: [], name: "MAIN APP", appID: 1, actionID: 6 },
};
serverData.actions[1].target = "fullscreen";
serverData.views["partner,false,form"] =
'<form><button name="24" type="action" class="oe_stat_button"/></form>';
const webClient = await createWebClient({ serverData });
await testUtils.nextTick(); // wait for the load state (default app)
await legacyExtraNextTick();
assert.containsOnce(webClient, "nav .o_menu_brand");
assert.strictEqual($(webClient.el).find("nav .o_menu_brand").text(), "MAIN APP");
assert.doesNotHaveClass(webClient.el, "o_fullscreen");
await testUtils.dom.click($(webClient.el).find('button[name="24"]'));
await legacyExtraNextTick();
assert.doesNotHaveClass(webClient.el, "o_fullscreen");
await testUtils.dom.click($(webClient.el).find('button[name="1"]'));
await legacyExtraNextTick();
assert.hasClass(webClient.el, "o_fullscreen");
await testUtils.dom.click($(webClient.el).find(".breadcrumb li a")[1]);
await legacyExtraNextTick();
assert.doesNotHaveClass(webClient.el, "o_fullscreen");
assert.containsOnce(webClient, "nav .o_menu_brand");
assert.strictEqual($(webClient.el).find("nav .o_menu_brand").text(), "MAIN APP");
}
);
});