import { describe, expect, test } from "@odoo/hoot"; import { queryAll, queryAllTexts, queryText } from "@odoo/hoot-dom"; import { animationFrame, Deferred } from "@odoo/hoot-mock"; import { Component, onMounted, xml } from "@odoo/owl"; import { contains, defineActions, defineMenus, defineModels, getService, mockService, models, mountWithCleanup, onRpc, patchWithCleanup, stepAllNetworkCalls, webModels, } from "@web/../tests/web_test_helpers"; import { ClientErrorDialog } from "@web/core/errors/error_dialogs"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { WebClient } from "@web/webclient/webclient"; const { ResCompany, ResPartner, ResUsers } = webModels; class Partner extends models.Model { _rec_name = "display_name"; _records = [ { id: 1, display_name: "First record" }, { id: 2, display_name: "Second record" }, ]; _views = { "form,false": `
`, "kanban,1": ` `, "list,false": ``, "list,2": ``, "search,false": ``, }; } defineModels([Partner, ResCompany, ResPartner, ResUsers]); defineActions([ { id: 1, xml_id: "action_1", name: "Partners Action 1", res_model: "partner", type: "ir.actions.act_window", views: [[1, "kanban"]], }, { id: 4, xml_id: "action_4", name: "Partners Action 4", res_model: "partner", type: "ir.actions.act_window", views: [ [1, "kanban"], [2, "list"], [false, "form"], ], }, { id: 5, xml_id: "action_5", name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[false, "form"]], }, { id: 15, name: "Partners Action Fullscreen", res_model: "partner", target: "fullscreen", type: "ir.actions.act_window", views: [[1, "kanban"]], }, ]); describe("new", () => { test('can execute act_window actions in target="new"', async () => { stepAllNetworkCalls(); await mountWithCleanup(WebClient); await getService("action").doAction(5); expect(".o_technical_modal .o_form_view").toHaveCount(1, { message: "should have rendered a form view in a modal", }); expect(".o_technical_modal .modal-body").toHaveClass("o_act_window", { message: "dialog main element should have classname 'o_act_window'", }); expect(".o_technical_modal .o_form_view .o_form_editable").toHaveCount(1, { message: "form view should be in edit mode", }); expect.verifySteps([ "/web/webclient/translations", "/web/webclient/load_menus", "/web/action/load", "get_views", "onchange", ]); }); test("chained action on_close", async () => { function onClose(closeInfo) { expect(closeInfo).toBe("smallCandle"); expect.step("Close Action"); } await mountWithCleanup(WebClient); await getService("action").doAction(5, { onClose }); // a target=new action shouldn't activate the on_close await getService("action").doAction(5); expect.verifySteps([]); // An act_window_close should trigger the on_close await getService("action").doAction({ type: "ir.actions.act_window_close", infos: "smallCandle", }); expect.verifySteps(["Close Action"]); }); test("footer buttons are moved to the dialog footer", async () => { Partner._views["form,false"] = `
`; await mountWithCleanup(WebClient); await getService("action").doAction(5); expect(".o_technical_modal .modal-body button.infooter").toHaveCount(0, { message: "the button should not be in the body", }); expect(".o_technical_modal .modal-footer button.infooter").toHaveCount(1, { message: "the button should be in the footer", }); expect(".modal-footer button:not(.d-none)").toHaveCount(1, { message: "the modal footer should only contain one visible button", }); }); test.tags("desktop"); test("Button with `close` attribute closes dialog on desktop", async () => { Partner._views = { "form,false": `
`, "form,17": `
`, "search,false": "", }; defineActions([ { id: 4, name: "Partners Action 4", res_model: "partner", type: "ir.actions.act_window", views: [[false, "form"]], }, { id: 5, name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[17, "form"]], }, ]); onRpc("/web/dataset/call_button/*", async (request) => { const { params } = await request.json(); if (params.method === "some_method") { return { tag: "display_notification", type: "ir.actions.client", }; } }); stepAllNetworkCalls(); await mountWithCleanup(WebClient); expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]); await getService("action").doAction(4); expect.verifySteps(["/web/action/load", "get_views", "onchange"]); await contains(`button[name="5"]`).click(); expect.verifySteps(["web_save", "/web/action/load", "get_views", "onchange"]); expect(".modal").toHaveCount(1); await contains(`button[name=some_method]`).click(); expect.verifySteps(["web_save", "some_method", "web_read"]); expect(".modal").toHaveCount(0); }); test.tags("mobile"); test("Button with `close` attribute closes dialog on mobile", async () => { Partner._views = { "form,false": `
`, "form,17": `
`, "search,false": "", }; defineActions([ { id: 4, name: "Partners Action 4", res_model: "partner", type: "ir.actions.act_window", views: [[false, "form"]], }, { id: 5, name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[17, "form"]], }, ]); onRpc("/web/dataset/call_button/*", async (request) => { const { params } = await request.json(); if (params.method === "some_method") { return { tag: "display_notification", type: "ir.actions.client", }; } }); stepAllNetworkCalls(); await mountWithCleanup(WebClient); expect.verifySteps(["/web/webclient/translations", "/web/webclient/load_menus"]); await getService("action").doAction(4); expect.verifySteps(["/web/action/load", "get_views", "onchange"]); await contains(`.o_cp_action_menus button:has(.fa-cog)`).click(); await contains(`button[name="5"]`).click(); expect.verifySteps(["web_save", "/web/action/load", "get_views", "onchange"]); expect(".modal").toHaveCount(1); await contains(`button[name=some_method]`).click(); expect.verifySteps(["web_save", "some_method", "web_read"]); expect(".modal").toHaveCount(0); }); test('footer buttons are updated when having another action in target "new"', async () => { defineActions([ { id: 25, name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[3, "form"]], }, ]); Partner._views = { "form,false": `
`, "form,3": `
`, }; await mountWithCleanup(WebClient); await getService("action").doAction(5); expect('.o_technical_modal .modal-body button[special="save"]').toHaveCount(0); expect(".o_technical_modal .modal-body button.infooter").toHaveCount(0); expect(".o_technical_modal .modal-footer button.infooter").toHaveCount(1); expect(".o_technical_modal .modal-footer button:not(.d-none)").toHaveCount(1); await getService("action").doAction(25); expect(".o_technical_modal .modal-body button.infooter").toHaveCount(0); expect(".o_technical_modal .modal-footer button.infooter").toHaveCount(0); expect('.o_technical_modal .modal-body button[special="save"]').toHaveCount(0); expect('.o_technical_modal .modal-footer button[special="save"]').toHaveCount(1); expect(".o_technical_modal .modal-footer button:not(.d-none)").toHaveCount(1); }); test('button with confirm attribute in act_window action in target="new"', async () => { defineActions([ { id: 999, name: "A window action", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[999, "form"]], }, ]); Partner._views["form,999"] = `