/** @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"] = `
`; 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`
`; 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, }); }); });