/** @odoo-module **/ import { dialogService } from "@web/core/dialog/dialog_service"; import { notificationService } from "@web/core/notifications/notification_service"; import { ormService } from "@web/core/orm_service"; import { popoverService } from "@web/core/popover/popover_service"; import { registry } from "@web/core/registry"; import { viewService } from "@web/views/view_service"; import { actionService } from "@web/webclient/actions/action_service"; import { effectService } from "@web/core/effects/effect_service"; import { hotkeyService } from "@web/core/hotkeys/hotkey_service"; import { menuService } from "@web/webclient/menus/menu_service"; import { WebClient } from "@web/webclient/webclient"; import { registerCleanup } from "../helpers/cleanup"; import { makeTestEnv } from "../helpers/mock_env"; import { fakeTitleService, fakeCompanyService, makeFakeLocalizationService, makeFakeRouterService, makeFakeHTTPService, makeFakeBarcodeService, makeFakeUserService, } from "../helpers/mock_services"; import { getFixture, mount, nextTick } from "../helpers/utils"; import { uiService } from "@web/core/ui/ui_service"; import { commandService } from "@web/core/commands/command_service"; import { CustomFavoriteItem } from "@web/search/custom_favorite_item/custom_favorite_item"; import { overlayService } from "@web/core/overlay/overlay_service"; import { Component, xml } from "@odoo/owl"; import { fieldService } from "@web/core/field_service"; import { nameService } from "@web/core/name_service"; import { datetimePickerService } from "@web/core/datetime/datetimepicker_service"; const actionRegistry = registry.category("actions"); const serviceRegistry = registry.category("services"); const favoriteMenuRegistry = registry.category("favoriteMenu"); /** * Builds the required registries for tests using a WebClient. * We use a default version of each required registry item. * If the registry already contains one of those items, * the existing one is kept (it means it has been added in the test * directly, e.g. to have a custom version of the item). */ export function setupWebClientRegistries() { const favoriveMenuItems = { "custom-favorite-item": { value: { Component: CustomFavoriteItem, groupNumber: 3 }, options: { sequence: 0 }, }, }; for (const [key, { value, options }] of Object.entries(favoriveMenuItems)) { if (!favoriteMenuRegistry.contains(key)) { favoriteMenuRegistry.add(key, value, options); } } const services = { action: () => actionService, barcode: () => makeFakeBarcodeService(), command: () => commandService, dialog: () => dialogService, effect: () => effectService, field: () => fieldService, hotkey: () => hotkeyService, http: () => makeFakeHTTPService(), localization: () => makeFakeLocalizationService(), menu: () => menuService, name: () => nameService, notification: () => notificationService, orm: () => ormService, overlay: () => overlayService, popover: () => popoverService, router: () => makeFakeRouterService(), title: () => fakeTitleService, ui: () => uiService, user: () => makeFakeUserService(), view: () => viewService, company: () => fakeCompanyService, datetime_picker: () => datetimePickerService, }; for (const serviceName in services) { if (!serviceRegistry.contains(serviceName)) { serviceRegistry.add(serviceName, services[serviceName]()); } } } /** * This method create a web client instance properly configured. * * Note that the returned web client will be automatically cleaned up after the * end of the test. * * @param {*} params */ export async function createWebClient(params) { setupWebClientRegistries(); params.serverData = params.serverData || {}; const mockRPC = params.mockRPC || undefined; const env = await makeTestEnv({ serverData: params.serverData, mockRPC, }); const WebClientClass = params.WebClientClass || WebClient; const target = params && params.target ? params.target : getFixture(); const wc = await mount(WebClientClass, target, { env }); odoo.__WOWL_DEBUG__ = { root: wc }; target.classList.add("o_web_client"); // necessary for the stylesheet registerCleanup(() => { target.classList.remove("o_web_client"); }); // Wait for visual changes caused by a potential loadState await nextTick(); return wc; } export function doAction(env, ...args) { if (env instanceof Component) { env = env.env; } return env.services.action.doAction(...args); } export async function loadState(env, state) { if (env instanceof Component) { env = env.env; } env.bus.trigger("test:hashchange", state); // wait the asynchronous hashchange // (the event hashchange must be triggered in a nonBlocking stack) await nextTick(); // wait for BlankComponent await nextTick(); // wait for the regular rendering await nextTick(); } export function getActionManagerServerData() { // additional basic client action class TestClientAction extends Component {} TestClientAction.template = xml`
ClientAction_
`; actionRegistry.add("__test__client__action__", TestClientAction); const menus = { root: { id: "root", children: [0, 1, 2], name: "root", appID: "root" }, // id:0 is a hack to not load anything at webClient mount 0: { id: 0, children: [], name: "UglyHack", appID: 0, xmlid: "menu_0" }, 1: { id: 1, children: [], name: "App1", appID: 1, actionID: 1001, xmlid: "menu_1" }, 2: { id: 2, children: [], name: "App2", appID: 2, actionID: 1002, xmlid: "menu_2" }, }; const actionsArray = [ { id: 1, xml_id: "action_1", name: "Partners Action 1", res_model: "partner", type: "ir.actions.act_window", views: [[1, "kanban"]], }, { id: 2, xml_id: "action_2", type: "ir.actions.server", }, { id: 3, xml_id: "action_3", name: "Partners", res_model: "partner", mobile_view_mode: "kanban", type: "ir.actions.act_window", views: [ [false, "list"], [1, "kanban"], [false, "form"], ], }, { 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: 6, xml_id: "action_6", name: "Partner", res_id: 2, res_model: "partner", target: "inline", type: "ir.actions.act_window", views: [[false, "form"]], }, { id: 7, xml_id: "action_7", name: "Some Report", report_name: "some_report", report_type: "qweb-pdf", type: "ir.actions.report", }, { id: 8, xml_id: "action_8", name: "Favorite Ponies", res_model: "pony", type: "ir.actions.act_window", views: [ [false, "list"], [false, "form"], ], }, { id: 9, xml_id: "action_9", name: "A Client Action", tag: "ClientAction", type: "ir.actions.client", }, { id: 10, type: "ir.actions.act_window_close", }, { id: 11, xml_id: "action_11", name: "Another Report", report_name: "another_report", report_type: "qweb-pdf", type: "ir.actions.report", close_on_report_download: true, }, { id: 12, xml_id: "action_12", name: "Some HTML Report", report_name: "some_report", report_type: "qweb-html", type: "ir.actions.report", }, { id: 24, name: "Partner", res_id: 2, res_model: "partner", type: "ir.actions.act_window", views: [[666, "form"]], }, { id: 25, name: "Create a Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[3, "form"]], }, { id: 26, xml_id: "action_26", name: "Partner", res_model: "partner", target: "new", type: "ir.actions.act_window", views: [[false, "list"]], }, { id: 1001, tag: "__test__client__action__", target: "main", type: "ir.actions.client", params: { description: "Id 1" }, }, { id: 1002, tag: "__test__client__action__", target: "main", type: "ir.actions.client", params: { description: "Id 2" }, }, { xmlId: "wowl.client_action", id: 1099, tag: "__test__client__action__", target: "main", type: "ir.actions.client", params: { description: "xmlId" }, }, ]; const actions = {}; actionsArray.forEach((act) => { actions[act.xmlId || act.id] = act; }); const archs = { // kanban views "partner,1,kanban": '' + '
' + "
", // list views "partner,false,list": '', "partner,2,list": '', "pony,false,list": '', // form views "partner,false,form": "
" + "
" + '
" + "" + '' + '' + "" + "
", "partner,3,form": `
`, "partner,666,form": `
`, "pony,false,form": "
" + '' + "", // search views "partner,false,search": '', "partner,4,search": "" + '' + "", "pony,false,search": "", }; const models = { partner: { fields: { id: { string: "Id", type: "integer" }, foo: { string: "Foo", type: "char" }, bar: { string: "Bar", type: "many2one", relation: "partner" }, o2m: { string: "One2Many", type: "one2many", relation: "partner", relation_field: "bar", }, m2o: { string: "Many2one", type: "many2one", relation: "partner" }, }, records: [ { id: 1, display_name: "First record", foo: "yop", bar: 2, o2m: [2, 3], m2o: 3 }, { id: 2, display_name: "Second record", foo: "blip", bar: 1, o2m: [1, 4, 5], m2o: 3, }, { id: 3, display_name: "Third record", foo: "gnap", bar: 1, o2m: [], m2o: 1 }, { id: 4, display_name: "Fourth record", foo: "plop", bar: 2, o2m: [], m2o: 1 }, { id: 5, display_name: "Fifth record", foo: "zoup", bar: 2, o2m: [], m2o: 1 }, ], }, pony: { fields: { id: { string: "Id", type: "integer" }, name: { string: "Name", type: "char" }, }, records: [ { id: 4, name: "Twilight Sparkle" }, { id: 6, name: "Applejack" }, { id: 9, name: "Fluttershy" }, ], }, }; return { models, views: archs, actions, menus, }; }