合并企业版代码(未测试,先提交到测试分支)

This commit is contained in:
qihao.gong@jikimo.com
2023-04-14 17:42:23 +08:00
parent 7a7b3d7126
commit d28525526a
1300 changed files with 513579 additions and 5426 deletions

View File

@@ -0,0 +1,784 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { click, getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services";
import { browser } from "@web/core/browser/browser";
import { ormService } from "@web/core/orm_service";
import testUtils from "web.test_utils";
import { enterpriseSubscriptionService } from "@web_enterprise/webclient/home_menu/enterprise_subscription_service";
import { homeMenuService } from "@web_enterprise/webclient/home_menu/home_menu_service";
import { session } from "@web/session";
const serviceRegistry = registry.category("services");
let target;
async function createExpirationPanel(params = {}) {
const mockedCookieService = {
name: "cookie",
start() {
return Object.assign(
{
current: {},
setCookie() {},
deleteCookie() {},
},
params.cookie
);
},
};
serviceRegistry.add(mockedCookieService.name, mockedCookieService);
serviceRegistry.add("orm", ormService);
serviceRegistry.add("home_menu", homeMenuService);
serviceRegistry.add(
"notification",
makeFakeNotificationService(params.mockNotification || (() => {}))
);
patchWithCleanup(session, { ...params.session });
serviceRegistry.add("enterprise_subscription", enterpriseSubscriptionService);
patchWithCleanup(browser, params.browser);
const webclient = await createWebClient({
mockRPC: params.mockRPC,
});
await doAction(webclient, "menu");
return webclient;
}
QUnit.module("web_enterprise", function ({ beforeEach }) {
beforeEach(() => {
target = getFixture();
});
QUnit.module("Expiration Panel");
QUnit.test("Expiration Panel one app installed", async function (assert) {
assert.expect(3);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-11-09 12:00:00",
expiration_reason: "",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 1 month."
);
// Color should be grey
assert.hasClass(target.querySelector(".database_expiration_panel"), "alert-info");
// Close the expiration panel
await click(target.querySelector(".oe_instance_hide_panel"));
assert.containsNone(target, ".database_expiration_panel");
});
QUnit.test("Expiration Panel one app installed, buy subscription", async function (assert) {
assert.expect(6);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-24 12:00:00",
expiration_reason: "demo",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route) {
if (route === "/web/dataset/call_kw/res.users/search_count") {
return 7;
}
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This demo database will expire in 14 days. Register your subscription or buy a subscription."
);
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-warning",
"Color should be orange"
);
assert.containsOnce(
target,
".oe_instance_register_show",
"Part 'Register your subscription'"
);
assert.containsOnce(target, ".oe_instance_buy", "Part 'buy a subscription'");
assert.containsNone(
target,
".oe_instance_register_form",
"There should be no registration form"
);
// Click on 'buy subscription'
await click(target.querySelector(".oe_instance_buy"));
});
QUnit.test(
"Expiration Panel one app installed, try several times to register subscription",
async function (assert) {
assert.expect(44);
patchDate(2019, 9, 10, 12, 0, 0);
let callToGetParamCount = 0;
await createExpirationPanel({
session: {
expiration_date: "2019-10-15 12:00:00",
expiration_reason: "trial",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockNotification(message, options) {
assert.step(JSON.stringify({ message, options }));
},
mockRPC(route, { args }) {
if (route === "/web/dataset/call_kw/ir.config_parameter/get_param") {
assert.step("get_param");
if (args[0] === "database.already_linked_subscription_url") {
return false;
}
if (args[0] === "database.already_linked_email") {
return "super_company_admin@gmail.com";
}
assert.strictEqual(args[0], "database.expiration_date");
callToGetParamCount++;
if (callToGetParamCount <= 3) {
return "2019-10-15 12:00:00";
} else {
return "2019-11-15 12:00:00";
}
}
if (route === "/web/dataset/call_kw/ir.config_parameter/set_param") {
assert.step("set_param");
assert.strictEqual(args[0], "database.enterprise_code");
if (callToGetParamCount === 1) {
assert.strictEqual(args[1], "ABCDEF");
} else {
assert.strictEqual(args[1], "ABC");
}
return true;
}
if (
route ===
"/web/dataset/call_kw/publisher_warranty.contract/update_notification"
) {
assert.step("update_notification");
assert.ok(args[0] instanceof Array && args[0].length === 0);
return true;
}
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 5 days. Register your subscription or buy a subscription."
);
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-danger",
"Color should be red"
);
assert.containsOnce(
target,
".oe_instance_register_show",
"Part 'Register your subscription'"
);
assert.containsOnce(target, ".oe_instance_buy", "Part 'buy a subscription'");
assert.containsNone(
target,
".oe_instance_register_form",
"There should be no registration form"
);
// Click on 'register your subscription'
await click(target.querySelector(".oe_instance_register_show"));
assert.containsOnce(
target,
".oe_instance_register_form",
"there should be a registration form"
);
assert.containsOnce(
target,
'.oe_instance_register_form input[placeholder="Paste code here"]',
"with an input with place holder 'Paste code here'"
);
assert.containsOnce(
target,
".oe_instance_register_form button",
"and a button 'REGISTER'"
);
assert.strictEqual(
target.querySelector(".oe_instance_register_form button").innerText,
"REGISTER"
);
await click(target.querySelector(".oe_instance_register_form button"));
assert.containsOnce(
target,
".oe_instance_register_form",
"there should be a registration form"
);
assert.containsOnce(
target,
'.oe_instance_register_form input[placeholder="Paste code here"]',
"with an input with place holder 'Paste code here'"
);
assert.containsOnce(
target,
".oe_instance_register_form button",
"and a button 'REGISTER'"
);
await testUtils.fields.editInput(
target.querySelector(".oe_instance_register_form input"),
"ABCDEF"
);
await click(target.querySelector(".oe_instance_register_form button"));
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"Something went wrong while registering your database. You can try again or contact Odoo Support."
);
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-danger",
"Color should be red"
);
assert.containsOnce(target, "span.oe_instance_error");
assert.containsOnce(
target,
".oe_instance_register_form",
"there should be a registration form"
);
assert.containsOnce(
target,
'.oe_instance_register_form input[placeholder="Paste code here"]',
"with an input with place holder 'Paste code here'"
);
assert.containsOnce(
target,
".oe_instance_register_form button",
"and a button 'REGISTER'"
);
assert.strictEqual(
target.querySelector(".oe_instance_register_form button").innerText,
"RETRY"
);
await testUtils.fields.editInput(
target.querySelector(".oe_instance_register_form input"),
"ABC"
);
await click(target.querySelector(".oe_instance_register_form button"));
assert.containsNone(
target,
".database_expiration_panel",
"expiration panel should be gone"
);
assert.verifySteps([
// second try to submit
"get_param",
"set_param",
"get_param",
"get_param",
"update_notification",
"get_param",
// third try
"get_param",
"set_param",
"get_param",
"get_param",
"update_notification",
"get_param",
`{"message":"Thank you, your registration was successful! Your database is valid until November 15, 2019.","options":{"type":"success"}}`,
]);
}
);
QUnit.test(
"Expiration Panel one app installed, subscription already linked",
async function (assert) {
assert.expect(12);
patchDate(2019, 9, 10, 12, 0, 0);
// There are some line breaks mismatches between local and runbot test instances.
// Since they don't affect the layout and we're only interested in the text itself,
// we normalize whitespaces and line breaks from both the expected and end result
const formatWhiteSpaces = (text) =>
text
.split(/[\n\s]/)
.filter((w) => w !== "")
.join(" ");
let getExpirationDateCount = 0;
await createExpirationPanel({
session: {
expiration_date: "2019-10-15 12:00:00",
expiration_reason: "trial",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { method, args }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (route === "/already/linked/send/mail/url") {
return {
result: false,
reason: "By design",
};
}
assert.step(method);
if (args[0] === "database.expiration_date") {
getExpirationDateCount++;
if (getExpirationDateCount === 1) {
return "2019-10-15 12:00:00";
} else {
return "2019-11-17 12:00:00";
}
}
if (args[0] === "database.already_linked_subscription_url") {
return "www.super_company.com";
}
if (args[0] === "database.already_linked_send_mail_url") {
return "/already/linked/send/mail/url";
}
if (args[0] === "database.already_linked_email") {
return "super_company_admin@gmail.com";
}
return true;
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 5 days. Register your subscription or buy a subscription."
);
// Click on 'register your subscription'
await click(target.querySelector(".oe_instance_register_show"));
await testUtils.fields.editInput(
target.querySelector(".oe_instance_register_form input"),
"ABC"
);
await click(target.querySelector(".oe_instance_register_form button"));
assert.strictEqual(
formatWhiteSpaces(
target.querySelector(".oe_instance_register.oe_database_already_linked")
.innerText
),
formatWhiteSpaces(
`Your subscription is already linked to a database.
To unlink it you can either:
Login to your Odoo.com dashboard then unlink your previous database: www.super_company.com
Click here to send an email to the subscription owner (email: super_company_admin@gmail.com) with the instructions to follow`
)
);
await click(target.querySelector("a.oe_contract_send_mail"));
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-danger",
"Color should be red"
);
assert.strictEqual(
formatWhiteSpaces(
target.querySelector(".oe_instance_register.oe_database_already_linked")
.innerText
),
formatWhiteSpaces(
`Your subscription is already linked to a database.
To unlink it you can either:
Login to your Odoo.com dashboard then unlink your previous database: www.super_company.com
Click here to send an email to the subscription owner (email: super_company_admin@gmail.com) with the instructions to follow
Unable to send the instructions by email, please contact the Odoo Support
Error reason: By design`
)
);
assert.verifySteps([
"get_param",
"set_param",
"get_param",
"get_param",
"update_notification",
"get_param",
"get_param",
]);
}
);
QUnit.test("One app installed, database expired", async function (assert) {
assert.expect(8);
patchDate(2019, 9, 10, 12, 0, 0);
let callToGetParamCount = 0;
await createExpirationPanel({
session: {
expiration_date: "2019-10-08 12:00:00",
expiration_reason: "trial",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { args, method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
if (args[0] === "database.already_linked_subscription_url") {
return false;
}
callToGetParamCount++;
if (callToGetParamCount === 1) {
return "2019-10-09 12:00:00";
} else {
return "2019-11-09 12:00:00";
}
}
return true;
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database has expired. Register your subscription or buy a subscription."
);
assert.containsOnce(target, ".o_blockUI", "UI should be blocked");
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-danger",
"Color should be red"
);
assert.containsOnce(
target,
".oe_instance_register_show",
"Part 'Register your subscription'"
);
assert.containsOnce(target, ".oe_instance_buy", "Part 'buy a subscription'");
assert.containsNone(target, ".oe_instance_register_form");
// Click on 'Register your subscription'
await click(target.querySelector(".oe_instance_register_show"));
await testUtils.fields.editInput(
target.querySelector(".oe_instance_register_form input"),
"ABC"
);
await click(target.querySelector(".oe_instance_register_form button"));
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"Thank you, your registration was successful! Your database is valid until November 9, 2019."
);
assert.containsNone(target, ".o_blockUI", "UI should no longer be blocked");
});
QUnit.test("One app installed, renew", async function (assert) {
assert.expect(8);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "renewal",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { args, method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
assert.step("get_param");
assert.strictEqual(args[0], "database.enterprise_code");
return "ABC";
}
return true;
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 10 days. Renew your subscription "
);
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-warning",
"Color should be red"
);
assert.containsOnce(target, ".oe_instance_renew", "Part 'Register your subscription'");
assert.containsOnce(
target,
"a.check_enterprise_status",
"there should be a button for status checking"
);
assert.containsNone(target, ".oe_instance_register_form");
// Click on 'Renew your subscription'
await click(target.querySelector(".oe_instance_renew"));
assert.verifySteps(["get_param"]);
});
QUnit.test("One app installed, check status and get success", async function (assert) {
assert.expect(6);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "renewal",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { args, method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
assert.step("get_param");
assert.strictEqual(args[0], "database.expiration_date");
return "2019-10-24 12:00:00";
}
if (method === "update_notification") {
assert.step("update_notification");
}
return true;
},
});
// click on "Refresh subscription status"
const refreshButton = target.querySelector("a.check_enterprise_status");
assert.strictEqual(refreshButton.getAttribute("aria-label"), "Refresh subscription status");
await click(refreshButton);
assert.strictEqual(
target.querySelector(".oe_instance_register.oe_subscription_updated").innerText,
"Your subscription was updated and is valid until October 24, 2019."
);
assert.verifySteps(["update_notification", "get_param"]);
});
// Why would we want to reload the page when we check the status and it hasn't changed?
QUnit.skip("One app installed, check status and get page reload", async function (assert) {
assert.expect(4);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "renewal",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
assert.step("get_param");
return "2019-10-20 12:00:00";
}
if (method === "update_notification") {
assert.step("update_notification");
}
return true;
},
});
// click on "Refresh subscription status"
await click(target.querySelector("a.check_enterprise_status"));
assert.verifySteps(["update_notification", "get_param", "reloadPage"]);
});
QUnit.test("One app installed, upgrade database", async function (assert) {
assert.expect(6);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "upsell",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { args, method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
assert.step("get_param");
assert.strictEqual(args[0], "database.enterprise_code");
return "ABC";
}
if (method === "search_count") {
assert.step("search_count");
return 13;
}
return true;
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 10 days. You have more users or more apps installed than your subscription allows.\n" +
"Upgrade your subscription "
);
// click on "Upgrade your subscription"
await click(target.querySelector("a.oe_instance_upsell"));
assert.verifySteps(["get_param", "search_count"]);
});
QUnit.test("One app installed, message for non admin user", async function (assert) {
assert.expect(2);
patchDate(2019, 9, 10, 12, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-11-08 12:00:00",
expiration_reason: "",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "user",
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database will expire in 29 days. Log in as an administrator to correct the issue."
);
assert.hasClass(
target.querySelector(".database_expiration_panel"),
"alert-info",
"Color should be grey"
);
});
QUnit.test("One app installed, navigation to renewal page", async function (assert) {
assert.expect(9);
patchDate(2019, 11, 10, 0, 0, 0);
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "renewal",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
mockRPC(route, { args, method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
assert.step("get_param");
assert.strictEqual(args[0], "database.enterprise_code");
return "ABC";
}
if (method === "update_notification") {
assert.step("update_notification");
}
return true;
},
});
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"This database has expired. Renew your subscription "
);
assert.hasClass(target.querySelector(".database_expiration_panel"), "alert-danger");
assert.containsOnce(target, ".oe_instance_renew", "Part 'Register your subscription'");
assert.containsOnce(
target,
"a.check_enterprise_status",
"there should be a button for status checking"
);
assert.containsNone(target, ".oe_instance_register_form");
// Click on 'Renew your subscription'
await click(target.querySelector(".oe_instance_renew"));
assert.verifySteps(["get_param"]);
});
QUnit.test("One app installed, different locale (arabic)", async function (assert) {
assert.expect(1);
patchDate(2019, 9, 25, 12, 0, 0);
patchWithCleanup(luxon.Settings, {
defaultLocale: "ar-001",
defaultNumberingSystem: "arab",
});
await createExpirationPanel({
session: {
expiration_date: "2019-10-20 12:00:00",
expiration_reason: "renewal",
notification_type: true, // used by subscription service to know whether mail is installed
warning: "admin",
},
async mockRPC(route, { method }) {
if (route === "/web/webclient/load_menus") {
return;
}
if (method === "get_param") {
return "2019-11-09 12:00:00";
}
return true;
},
});
await click(target, ".check_enterprise_status");
assert.strictEqual(
target.querySelector(".oe_instance_register").innerText,
"Your subscription was updated and is valid until ٩ نوفمبر ٢٠١٩."
);
});
});

View File

@@ -0,0 +1,370 @@
/** @odoo-module **/
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
import { getFixture, nextTick, triggerHotkey, patchWithCleanup } from "@web/../tests/helpers/utils";
import { commandService } from "@web/core/commands/command_service";
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
import { ormService } from "@web/core/orm_service";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { HomeMenu } from "@web_enterprise/webclient/home_menu/home_menu";
import testUtils from "web.test_utils";
import { enterpriseSubscriptionService } from "@web_enterprise/webclient/home_menu/enterprise_subscription_service";
import { session } from "@web/session";
import { templates } from "@web/core/assets";
const { App, EventBus } = owl;
const patchDate = testUtils.mock.patchDate;
const serviceRegistry = registry.category("services");
let target;
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
async function createHomeMenu(homeMenuProps) {
const env = await makeTestEnv();
const app = new App(HomeMenu, {
env,
props: homeMenuProps,
templates,
test: true,
});
const homeMenu = await app.mount(target);
registerCleanup(() => app.destroy());
return homeMenu;
}
async function walkOn(assert, path) {
for (const step of path) {
triggerHotkey(`${step.shiftKey ? "shift+" : ""}${step.key}`);
await nextTick();
assert.hasClass(
target.querySelectorAll(".o_menuitem")[step.index],
"o_focused",
`step ${step.number}`
);
}
}
// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------
let homeMenuProps;
let bus;
QUnit.module(
"web_enterprise",
{
beforeEach: function () {
homeMenuProps = {
apps: [
{
actionID: 121,
appID: 1,
id: 1,
label: "Discuss",
parents: "",
webIcon: false,
xmlid: "app.1",
},
{
actionID: 122,
appID: 2,
id: 2,
label: "Calendar",
parents: "",
webIcon: false,
xmlid: "app.2",
},
{
actionID: 123,
appID: 3,
id: 3,
label: "Contacts",
parents: "",
webIcon: false,
xmlid: "app.3",
},
],
};
bus = new EventBus();
const fakeHomeMenuService = {
name: "home_menu",
start() {
return {
toggle(show) {
bus.trigger("toggle", show);
},
};
},
};
const fakeMenuService = {
name: "menu",
start() {
return {
selectMenu(menu) {
bus.trigger("selectMenu", menu.id);
},
getMenu() {
return {};
},
};
},
};
serviceRegistry.add("ui", uiService);
serviceRegistry.add("hotkey", hotkeyService);
serviceRegistry.add("command", commandService);
serviceRegistry.add("localization", makeFakeLocalizationService());
serviceRegistry.add("orm", ormService);
serviceRegistry.add(enterpriseSubscriptionService.name, enterpriseSubscriptionService);
serviceRegistry.add(fakeHomeMenuService.name, fakeHomeMenuService);
serviceRegistry.add(fakeMenuService.name, fakeMenuService);
target = getFixture();
},
},
function () {
QUnit.module("HomeMenu");
QUnit.test("ESC Support", async function (assert) {
bus.on("toggle", null, (show) => {
assert.step(`toggle ${show}`);
});
await createHomeMenu(homeMenuProps);
await testUtils.dom.triggerEvent(window, "keydown", { key: "Escape" });
assert.verifySteps(["toggle false"]);
});
QUnit.test("Click on an app", async function (assert) {
bus.on("selectMenu", null, (menuId) => {
assert.step(`selectMenu ${menuId}`);
});
await createHomeMenu(homeMenuProps);
await testUtils.dom.click(target.querySelectorAll(".o_menuitem")[0]);
assert.verifySteps(["selectMenu 1"]);
});
QUnit.test("Display Expiration Panel (no module installed)", async function (assert) {
const unpatchDate = patchDate(2019, 9, 10, 0, 0, 0);
registerCleanup(unpatchDate);
patchWithCleanup(session, {
expiration_date: "2019-11-01 12:00:00",
expiration_reason: "",
isMailInstalled: false,
warning: "admin",
});
let cookie = false;
const mockedCookieService = {
name: "cookie",
start() {
return {
get current() {
return cookie;
},
setCookie() {
cookie = true;
},
};
},
};
serviceRegistry.add(mockedCookieService.name, mockedCookieService);
await createHomeMenu(homeMenuProps);
assert.containsOnce(target, ".database_expiration_panel");
assert.strictEqual(
target.querySelector(".database_expiration_panel .oe_instance_register").innerText,
"You will be able to register your database once you have installed your first app.",
"There should be an expiration panel displayed"
);
// Close the expiration panel
await testUtils.dom.click(
target.querySelector(".database_expiration_panel .oe_instance_hide_panel")
);
assert.containsNone(target, ".database_expiration_panel");
});
QUnit.test("Navigation (only apps, only one line)", async function (assert) {
assert.expect(8);
homeMenuProps = {
apps: new Array(3).fill().map((x, i) => {
return {
actionID: 120 + i,
appID: i + 1,
id: i + 1,
label: `0${i}`,
parents: "",
webIcon: false,
xmlid: `app.${i}`,
};
}),
};
await createHomeMenu(homeMenuProps);
const path = [
{ number: 0, key: "ArrowDown", index: 0 },
{ number: 1, key: "ArrowRight", index: 1 },
{ number: 2, key: "Tab", index: 2 },
{ number: 3, key: "ArrowRight", index: 0 },
{ number: 4, key: "Tab", shiftKey: true, index: 2 },
{ number: 5, key: "ArrowLeft", index: 1 },
{ number: 6, key: "ArrowDown", index: 1 },
{ number: 7, key: "ArrowUp", index: 1 },
];
await walkOn(assert, path);
});
QUnit.test("Navigation (only apps, two lines, one incomplete)", async function (assert) {
assert.expect(19);
homeMenuProps = {
apps: new Array(8).fill().map((x, i) => {
return {
actionID: 121,
appID: i + 1,
id: i + 1,
label: `0${i}`,
parents: "",
webIcon: false,
xmlid: `app.${i}`,
};
}),
};
await createHomeMenu(homeMenuProps);
const path = [
{ number: 1, key: "ArrowRight", index: 0 },
{ number: 2, key: "ArrowUp", index: 6 },
{ number: 3, key: "ArrowUp", index: 0 },
{ number: 4, key: "ArrowDown", index: 6 },
{ number: 5, key: "ArrowDown", index: 0 },
{ number: 6, key: "ArrowRight", index: 1 },
{ number: 7, key: "ArrowRight", index: 2 },
{ number: 8, key: "ArrowUp", index: 7 },
{ number: 9, key: "ArrowUp", index: 1 },
{ number: 10, key: "ArrowRight", index: 2 },
{ number: 11, key: "ArrowDown", index: 7 },
{ number: 12, key: "ArrowDown", index: 1 },
{ number: 13, key: "ArrowUp", index: 7 },
{ number: 14, key: "ArrowRight", index: 6 },
{ number: 15, key: "ArrowLeft", index: 7 },
{ number: 16, key: "ArrowUp", index: 1 },
{ number: 17, key: "ArrowLeft", index: 0 },
{ number: 18, key: "ArrowLeft", index: 5 },
{ number: 19, key: "ArrowRight", index: 0 },
];
await walkOn(assert, path);
});
QUnit.test("Navigation and open an app in the home menu", async function (assert) {
assert.expect(7);
bus.on("selectMenu", null, (menuId) => {
assert.step(`selectMenu ${menuId}`);
});
await createHomeMenu(homeMenuProps);
// No app selected so nothing to open
await testUtils.dom.triggerEvent(window, "keydown", { key: "Enter" });
assert.verifySteps([]);
const path = [
{ number: 0, key: "ArrowDown", index: 0 },
{ number: 1, key: "ArrowRight", index: 1 },
{ number: 2, key: "Tab", index: 2 },
{ number: 3, key: "shift+Tab", index: 1 },
];
await walkOn(assert, path);
// open first app (Calendar)
await testUtils.dom.triggerEvent(window, "keydown", { key: "Enter" });
assert.verifySteps(["selectMenu 2"]);
});
QUnit.test(
"The HomeMenu input takes the focus when you press a key only if no other element is the activeElement",
async function (assert) {
const target = getFixture();
const homeMenu = await createHomeMenu(homeMenuProps);
const input = target.querySelector(".o_search_hidden");
assert.strictEqual(document.activeElement, input);
const activeElement = document.createElement("div");
homeMenu.env.services.ui.activateElement(activeElement);
// remove the focus from the input
const otherInput = document.createElement("input");
target.querySelector(".o_home_menu").appendChild(otherInput);
otherInput.focus();
otherInput.blur();
assert.notEqual(document.activeElement, input);
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.notEqual(document.activeElement, input);
homeMenu.env.services.ui.deactivateElement(activeElement);
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.strictEqual(document.activeElement, input);
}
);
QUnit.test(
"The HomeMenu input does not take the focus if it is already on another input",
async function (assert) {
const target = getFixture();
await createHomeMenu(homeMenuProps);
const homeMenuInput = target.querySelector(".o_search_hidden");
assert.strictEqual(document.activeElement, homeMenuInput);
const otherInput = document.createElement("input");
target.querySelector(".o_home_menu").appendChild(otherInput);
otherInput.focus();
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.notEqual(document.activeElement, homeMenuInput);
otherInput.remove();
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.strictEqual(document.activeElement, homeMenuInput);
}
);
QUnit.test(
"The HomeMenu input does not take the focus if it is already on a textarea",
async function (assert) {
const target = getFixture();
await createHomeMenu(homeMenuProps);
const homeMenuInput = target.querySelector(".o_search_hidden");
assert.strictEqual(document.activeElement, homeMenuInput);
const textarea = document.createElement("textarea");
target.querySelector(".o_home_menu").appendChild(textarea);
textarea.focus();
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.notEqual(document.activeElement, homeMenuInput);
textarea.remove();
await testUtils.dom.triggerEvent(window, "keydown", { key: "a" });
await nextTick();
assert.strictEqual(document.activeElement, homeMenuInput);
}
);
}
);

View File

@@ -0,0 +1,528 @@
/** @odoo-module **/
import {
click,
editInput,
getFixture,
nextTick,
patchWithCleanup,
} from "@web/../tests/helpers/utils";
import { doAction, getActionManagerServerData, loadState } from "@web/../tests/webclient/helpers";
import { registry } from "@web/core/registry";
import { editView } from "@web/views/debug_items";
import { createEnterpriseWebClient } from "@web_enterprise/../tests/helpers";
import { homeMenuService } from "@web_enterprise/webclient/home_menu/home_menu_service";
import { ormService } from "@web/core/orm_service";
import { enterpriseSubscriptionService } from "@web_enterprise/webclient/home_menu/enterprise_subscription_service";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { errorService } from "@web/core/errors/error_service";
import { browser } from "@web/core/browser/browser";
const { Component, xml } = owl;
let serverData;
let fixture;
const serviceRegistry = registry.category("services");
// Should test ONLY the webClient and features present in Enterprise
// Those tests rely on hidden view to be in CSS: display: none
QUnit.module("WebClient Enterprise", (hooks) => {
hooks.beforeEach(() => {
serverData = getActionManagerServerData();
fixture = getFixture();
serviceRegistry.add("home_menu", homeMenuService);
serviceRegistry.add("orm", ormService);
serviceRegistry.add("enterprise_subscription", enterpriseSubscriptionService);
});
QUnit.module("basic flow with home menu", (hooks) => {
let mockRPC;
hooks.beforeEach((assert) => {
serverData.menus[1].actionID = 4;
serverData.menus.root.children = [1];
serverData.views["partner,false,form"] = `
<form>
<field name="display_name"/>
<field name="m2o" open_target="current"/>
</form>`;
mockRPC = async (route) => {
assert.step(route);
if (route === "/web/dataset/call_kw/partner/get_formview_action") {
return {
type: "ir.actions.act_window",
res_model: "partner",
view_type: "form",
view_mode: "form",
views: [[false, "form"]],
target: "current",
res_id: 2,
};
}
};
});
QUnit.test("1 -- start up", async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
assert.ok(document.body.classList.contains("o_home_menu_background"));
assert.containsOnce(fixture, ".o_home_menu");
assert.isNotVisible(fixture.querySelector(".o_menu_toggle"));
assert.containsOnce(fixture, ".o_app.o_menuitem");
});
QUnit.test("2 -- navbar updates on displaying an action", async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await click(fixture.querySelector(".o_app.o_menuitem"));
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
assert.notOk(document.body.classList.contains("o_home_menu_background"));
assert.containsNone(fixture, ".o_home_menu");
assert.containsOnce(fixture, ".o_kanban_view");
const menuToggle = fixture.querySelector(".o_menu_toggle");
assert.isVisible(menuToggle);
assert.notOk(menuToggle.classList.contains("o_menu_toggle_back"));
});
QUnit.test("3 -- push another action in the breadcrumb", async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await click(fixture.querySelector(".o_app.o_menuitem"));
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
await click(fixture.querySelector(".o_kanban_record"));
await nextTick(); // there is another tick to update navbar and destroy HomeMenu
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
assert.isVisible(fixture.querySelector(".o_menu_toggle"));
assert.containsOnce(fixture, ".o_form_view");
assert.strictEqual(
fixture.querySelector(".breadcrumb-item.active").textContent,
"First record"
);
});
QUnit.test("4 -- push a third action in the breadcrumb", async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await click(fixture.querySelector(".o_app.o_menuitem"));
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
await click(fixture.querySelector(".o_kanban_record"));
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
await click(fixture, '.o_field_widget[name="m2o"] .o_external_button', true);
assert.verifySteps([
"/web/dataset/call_kw/partner/get_formview_action",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
assert.containsOnce(fixture, ".o_form_view");
assert.strictEqual(
fixture.querySelector(".breadcrumb-item.active").textContent,
"Second record"
);
assert.containsN(fixture, ".breadcrumb-item", 3);
});
QUnit.test(
"5 -- switch to HomeMenu from an action with 2 breadcrumbs",
async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await click(fixture.querySelector(".o_app.o_menuitem"));
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
await click(fixture.querySelector(".o_kanban_record"));
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
await click(fixture, '.o_field_widget[name="m2o"] .o_external_button', true);
assert.verifySteps([
"/web/dataset/call_kw/partner/get_formview_action",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
const menuToggle = fixture.querySelector(".o_menu_toggle");
await click(menuToggle);
assert.verifySteps([]);
assert.ok(menuToggle.classList.contains("o_menu_toggle_back"));
assert.containsOnce(fixture, ".o_home_menu");
assert.isNotVisible(fixture.querySelector(".o_form_view"));
}
);
QUnit.test("6 -- back to underlying action with many breadcrumbs", async function (assert) {
await createEnterpriseWebClient({ fixture, serverData, mockRPC });
assert.verifySteps(["/web/webclient/load_menus"]);
await click(fixture.querySelector(".o_app.o_menuitem"));
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/web_search_read",
]);
await click(fixture.querySelector(".o_kanban_record"));
assert.verifySteps(["/web/dataset/call_kw/partner/read"]);
await click(fixture, '.o_field_widget[name="m2o"] .o_external_button', true);
assert.verifySteps([
"/web/dataset/call_kw/partner/get_formview_action",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
const menuToggle = fixture.querySelector(".o_menu_toggle");
await click(menuToggle);
await click(menuToggle);
// if we don't reload on going back to underlying action
// assert.verifySteps(
// [],
// "the underlying view should not reload when toggling the HomeMenu to off"
// );
// endif
// if we reload on going back to underlying action
assert.verifySteps(
["/web/dataset/call_kw/partner/read"],
"the underlying view should reload when toggling the HomeMenu to off"
);
// endif
assert.containsNone(fixture, ".o_home_menu");
assert.containsOnce(fixture, ".o_form_view");
assert.notOk(menuToggle.classList.contains("o_menu_toggle_back"));
assert.strictEqual(
fixture.querySelector(".breadcrumb-item.active").textContent,
"Second record"
);
assert.containsN(fixture, ".breadcrumb-item", 3);
});
QUnit.test("restore the newly created record in form view (legacy)", async (assert) => {
const action = serverData.actions[6];
delete action.res_id;
action.target = "current";
const webClient = await createEnterpriseWebClient({ fixture, serverData });
await doAction(webClient, 6);
assert.containsOnce(fixture, ".o_form_view");
assert.containsOnce(fixture, ".o_form_view .o_form_editable");
await editInput(fixture, ".o_field_widget[name=display_name] input", "red right hand");
await click(fixture.querySelector(".o_form_button_save"));
assert.strictEqual(
fixture.querySelector(".breadcrumb-item.active").textContent,
"red right hand"
);
await click(fixture.querySelector(".o_menu_toggle"));
assert.isNotVisible(fixture.querySelector(".o_form_view"));
await click(fixture.querySelector(".o_menu_toggle"));
assert.containsOnce(fixture, ".o_form_view");
assert.containsOnce(fixture, ".o_form_view .o_form_saved");
assert.strictEqual(
fixture.querySelector(".breadcrumb-item.active").textContent,
"red right hand"
);
});
QUnit.skip("fast clicking on restore (implementation detail)", async (assert) => {
assert.expect(6);
let doVeryFastClick = false;
class DelayedClientAction extends Component {
setup() {
owl.onMounted(() => {
if (doVeryFastClick) {
doVeryFastClick = false;
click(fixture.querySelector(".o_menu_toggle"));
}
});
}
}
DelayedClientAction.template = xml`<div class='delayed_client_action'>
<button t-on-click="resolve">RESOLVE</button>
</div>`;
registry.category("actions").add("DelayedClientAction", DelayedClientAction);
const webClient = await createEnterpriseWebClient({ fixture, serverData });
await doAction(webClient, "DelayedClientAction");
await nextTick();
await click(fixture.querySelector(".o_menu_toggle"));
assert.isVisible(fixture.querySelector(".o_home_menu"));
assert.isNotVisible(fixture.querySelector(".delayed_client_action"));
doVeryFastClick = true;
await click(fixture.querySelector(".o_menu_toggle"));
await nextTick();
// off homemenu
assert.isVisible(fixture.querySelector(".o_home_menu"));
assert.isNotVisible(fixture.querySelector(".delayed_client_action"));
await click(fixture.querySelector(".o_menu_toggle"));
await nextTick();
assert.containsNone(fixture, ".o_home_menu");
assert.containsOnce(fixture, ".delayed_client_action");
});
});
QUnit.test("clear unCommittedChanges when toggling home menu", async function (assert) {
assert.expect(6);
// Edit a form view, don't save, toggle home menu
// the autosave feature of the Form view is activated
// and relied upon by this test
const mockRPC = (route, args) => {
if (args.method === "create") {
assert.strictEqual(args.model, "partner");
assert.deepEqual(args.args, [
{
display_name: "red right hand",
foo: false,
},
]);
}
};
const webClient = await createEnterpriseWebClient({ fixture, serverData, mockRPC });
await doAction(webClient, 3, { viewType: "form" });
assert.containsOnce(fixture, ".o_form_view .o_form_editable");
await editInput(fixture, ".o_field_widget[name=display_name] input", "red right hand");
await click(fixture.querySelector(".o_menu_toggle"));
assert.containsNone(fixture, ".o_form_view");
assert.containsNone(fixture, ".modal");
assert.containsOnce(fixture, ".o_home_menu");
});
QUnit.test("can have HomeMenu and dialog action", async function (assert) {
const webClient = await createEnterpriseWebClient({ fixture, serverData });
assert.containsOnce(fixture, ".o_home_menu");
assert.containsNone(fixture, ".modal .o_form_view");
await doAction(webClient, 5);
assert.containsOnce(fixture, ".modal .o_form_view");
assert.isVisible(fixture.querySelector(".modal .o_form_view"));
assert.containsOnce(fixture, ".o_home_menu");
});
QUnit.test("supports attachments of apps deleted", async function (assert) {
// When doing a pg_restore without the filestore
// LPE fixme: may not be necessary anymore since menus are not HomeMenu props anymore
serverData.menus = {
root: { id: "root", children: [1], name: "root", appID: "root" },
1: {
id: 1,
appID: 1,
actionID: 1,
xmlid: "",
name: "Partners",
children: [],
webIconData: "",
webIcon: "bloop,bloop",
},
};
patchWithCleanup(odoo, { debug: "1" });
await createEnterpriseWebClient({ fixture, serverData });
assert.containsOnce(fixture, ".o_home_menu");
});
QUnit.test(
"debug manager resets to global items when home menu is displayed",
async function (assert) {
const debugRegistry = registry.category("debug");
debugRegistry.category("view").add("editView", editView);
debugRegistry.category("default").add("item_1", () => {
return {
type: "item",
description: "globalItem",
callback: () => {},
sequence: 10,
};
});
const mockRPC = async (route) => {
if (route.includes("check_access_rights")) {
return true;
}
};
patchWithCleanup(odoo, { debug: "1" });
const webClient = await createEnterpriseWebClient({ fixture, serverData, mockRPC });
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
assert.containsOnce(fixture, ".o_debug_manager .dropdown-item:contains('globalItem')");
assert.containsNone(
fixture,
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
await doAction(webClient, 1);
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
assert.containsOnce(fixture, ".o_debug_manager .dropdown-item:contains('globalItem')");
assert.containsOnce(
fixture,
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
await click(fixture.querySelector(".o_menu_toggle"));
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
assert.containsOnce(fixture, ".o_debug_manager .dropdown-item:contains('globalItem')");
assert.containsNone(
fixture,
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
await doAction(webClient, 3);
await click(fixture.querySelector(".o_debug_manager .dropdown-toggle"));
assert.containsOnce(fixture, ".o_debug_manager .dropdown-item:contains('globalItem')");
assert.containsOnce(
fixture,
".o_debug_manager .dropdown-item:contains('Edit View: List')"
);
assert.containsNone(
fixture,
".o_debug_manager .dropdown-item:contains('Edit View: Kanban')"
);
}
);
QUnit.test(
"url state is well handled when going in and out of the HomeMenu",
async function (assert) {
const webClient = await createEnterpriseWebClient({ fixture, serverData });
await nextTick();
assert.deepEqual(webClient.env.services.router.current.hash, { action: "menu" });
await click(fixture.querySelector(".o_app.o_menuitem:nth-child(2)"));
await nextTick();
assert.deepEqual(webClient.env.services.router.current.hash, {
action: 1002,
menu_id: 2,
});
await click(fixture.querySelector(".o_menu_toggle"));
await nextTick();
assert.deepEqual(webClient.env.services.router.current.hash, { action: "menu" });
await click(fixture.querySelector(".o_menu_toggle"));
await nextTick();
// if we reload on going back to underlying action
// end if
assert.deepEqual(webClient.env.services.router.current.hash, {
action: 1002,
menu_id: 2,
});
}
);
QUnit.test(
"underlying action's menu items are invisible when HomeMenu is displayed",
async function (assert) {
serverData.menus[1].children = [99];
serverData.menus[99] = {
id: 99,
children: [],
name: "SubMenu",
appID: 1,
actionID: 1002,
xmlid: "",
webIconData: undefined,
webIcon: false,
};
await createEnterpriseWebClient({ fixture, serverData });
assert.containsNone(fixture, "nav .o_menu_sections");
assert.containsNone(fixture, "nav .o_menu_brand");
await click(fixture.querySelector(".o_app.o_menuitem:nth-child(1)"));
await nextTick();
assert.containsOnce(fixture, "nav .o_menu_sections");
assert.containsOnce(fixture, "nav .o_menu_brand");
assert.isVisible(fixture.querySelector(".o_menu_sections"));
assert.isVisible(fixture.querySelector(".o_menu_brand"));
await click(fixture.querySelector(".o_menu_toggle"));
assert.containsOnce(fixture, "nav .o_menu_sections");
assert.containsOnce(fixture, "nav .o_menu_brand");
assert.isNotVisible(fixture.querySelector(".o_menu_sections"));
assert.isNotVisible(fixture.querySelector(".o_menu_brand"));
}
);
QUnit.test("loadState back and forth keeps relevant keys in state", async function (assert) {
const webClient = await createEnterpriseWebClient({ fixture, serverData });
await click(fixture.querySelector(".o_app.o_menuitem:nth-child(2)"));
await nextTick();
assert.containsOnce(fixture, ".test_client_action");
assert.containsNone(fixture, ".o_home_menu");
const state = webClient.env.services.router.current.hash;
assert.deepEqual(state, {
action: 1002,
menu_id: 2,
});
await loadState(webClient, {});
assert.containsNone(fixture, ".test_client_action");
assert.containsOnce(fixture, ".o_home_menu");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: "menu",
});
await loadState(webClient, state);
assert.containsOnce(fixture, ".test_client_action");
assert.containsNone(fixture, ".o_home_menu");
assert.deepEqual(webClient.env.services.router.current.hash, state);
});
QUnit.test(
"go back to home menu using browser back button (i.e. loadState)",
async function (assert) {
const webClient = await createEnterpriseWebClient({ fixture, serverData });
assert.containsOnce(fixture, ".o_home_menu");
assert.isNotVisible(fixture.querySelector(".o_main_navbar .o_menu_toggle"));
await click(fixture.querySelector(".o_app.o_menuitem:nth-child(2)"));
assert.containsOnce(fixture, ".test_client_action");
assert.containsNone(fixture, ".o_home_menu");
await loadState(webClient, { action: "menu" }); // FIXME: this might need to be changed
assert.containsNone(fixture, ".test_client_action");
assert.containsOnce(fixture, ".o_home_menu");
assert.isNotVisible(fixture.querySelector(".o_main_navbar .o_menu_toggle"));
}
);
QUnit.test("initial action crashes", async (assert) => {
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: () => {},
});
browser.location.hash = "#action=__test__client__action__&menu_id=1";
const ClientAction = registry.category("actions").get("__test__client__action__");
class Override extends ClientAction {
setup() {
super.setup();
assert.step("clientAction setup");
throw new Error("my error");
}
}
registry.category("actions").add("__test__client__action__", Override, { force: true });
registry.category("services").add("error", errorService);
const webClient = await createEnterpriseWebClient({ fixture, serverData });
assert.verifySteps(["clientAction setup"]);
assert.containsOnce(fixture, "nav .o_menu_toggle");
assert.isVisible(fixture.querySelector("nav .o_menu_toggle"));
assert.strictEqual(fixture.querySelector(".o_action_manager").innerHTML, "");
assert.deepEqual(webClient.env.services.router.current.hash, {
action: "__test__client__action__",
menu_id: 1,
});
});
});