/** @odoo-module **/ import { makeTestEnv } from "@web/../tests/helpers/mock_env"; import { click, getFixture, makeDeferred, mount, nextTick, patchWithCleanup, } from "@web/../tests/helpers/utils"; import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers"; import { makeView } from "@web/../tests/views/helpers"; import { registry } from "@web/core/registry"; import { OnboardingBanner } from "@web/views/onboarding_banner"; import { View } from "@web/views/view"; import { actionService } from "@web/webclient/actions/action_service"; const { Component, useState, xml } = owl; const serviceRegistry = registry.category("services"); const viewRegistry = registry.category("views"); let serverData; QUnit.module("Views", (hooks) => { hooks.beforeEach(async () => { serverData = { models: { animal: { fields: { birthday: { string: "Birthday", type: "date", store: true }, type: { string: "Type", type: "selection", selection: [ ["omnivorous", "Omnivorous"], ["herbivorous", "Herbivorous"], ["carnivorous", "Carnivorous"], ], store: true, }, }, filters: [ // should be a model! { context: "{}", domain: "[('animal', 'ilike', 'o')]", id: 7, is_default: true, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ], }, }, views: { "animal,false,toy": `Arch content (id=false)`, "animal,1,toy": `Arch content (id=1)`, "animal,2,toy": `Arch content (id=2)`, "animal,false,other": ``, "animal,false,search": ``, "animal,1,search": ` `, }, }; class ToyView extends Component { setup() { this.class = "o_toy_view"; this.template = xml`${this.props.arch}`; } } ToyView.template = xml`
`; ToyView.type = "toy"; ToyView.components = { Banner: OnboardingBanner }; class ToyViewImp extends ToyView { setup() { super.setup(); this.class = "o_toy_view_imp"; } } viewRegistry.add("toy", ToyView); viewRegistry.add("toy_imp", ToyViewImp); setupControlPanelServiceRegistry(); const fakeActionService = { name: "action", start() { return { doAction() {}, }; }, }; serviceRegistry.add("action", fakeActionService, { force: true }); }); QUnit.module("View component"); //////////////////////////////////////////////////////////////////////////// // load_views //////////////////////////////////////////////////////////////////////////// QUnit.test("simple rendering", async function (assert) { assert.expect(11); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info, className } = this.props; assert.strictEqual(arch, serverData.views["animal,false,toy"]); assert.strictEqual(className, "o_action o_view_controller"); assert.deepEqual(fields, serverData.models.animal.fields); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, false); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { assert.strictEqual(args.model, "animal"); assert.strictEqual(args.method, "load_views"); assert.deepEqual(args.kwargs.views, [[false, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,false,toy"]); }); QUnit.test("rendering with given viewId", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, serverData.views["animal,1,toy"]); assert.deepEqual(fields, serverData.models.animal.fields); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { assert.deepEqual(args.kwargs.views, [[1, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", viewId: 1, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,1,toy"]); }); QUnit.test("rendering with given 'views' param", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, serverData.views["animal,1,toy"]); assert.deepEqual(fields, serverData.models.animal.fields); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { console.log(_); assert.deepEqual(args.kwargs.views, [[1, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", config: { views: [[1, "toy"]], }, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,1,toy"]); }); QUnit.test( "rendering with given 'views' param not containing view id", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, serverData.views["animal,false,toy"]); assert.deepEqual(fields, serverData.models.animal.fields); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, false); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { assert.deepEqual(args.kwargs.views, [ [false, "other"], [false, "toy"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", config: { views: [[false, "other"]], }, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,false,toy"]); } ); QUnit.test("viewId defined as prop and in 'views' prop", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, serverData.views["animal,1,toy"]); assert.deepEqual(fields, serverData.models.animal.fields); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { assert.deepEqual(args.kwargs.views, [ [1, "toy"], [false, "other"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", viewId: 1, config: { views: [ [3, "toy"], [false, "other"], ], }, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,1,toy"]); }); QUnit.test("rendering with given arch and fields", async function (assert) { assert.expect(6); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, `Specific arch content`); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, undefined); }, }); const view = await makeView({ serverData, mockRPC: () => { throw new Error("no RPC expected"); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, `Specific arch content`); }); QUnit.test("rendering with loadActionMenus='true'", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, serverData.views["animal,false,toy"]); assert.deepEqual(fields, serverData.models.animal.fields); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, false); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { // the rpc is done for fields assert.deepEqual(args.kwargs.views, [[false, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: true, }); }, resModel: "animal", type: "toy", loadActionMenus: true, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, serverData.views["animal,false,toy"]); }); QUnit.test( "rendering with given arch, fields, and loadActionMenus='true'", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, `Specific arch content`); assert.deepEqual(fields, {}); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, false); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { // the rpc is done for fields assert.deepEqual(args.kwargs.views, [[false, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: true, }); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, loadActionMenus: true, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, `Specific arch content`); } ); QUnit.test( "rendering with given arch, fields, actionMenus, and loadActionMenus='true'", async function (assert) { assert.expect(6); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; assert.strictEqual(arch, `Specific arch content`); assert.deepEqual(fields, {}); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, undefined); }, }); const view = await makeView({ serverData, mockRPC: () => { throw new Error("no RPC expected"); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, actionMenus: { /** ... */ }, loadActionMenus: true, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerHTML, `Specific arch content`); } ); QUnit.test("rendering with given searchViewId", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { irFilters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; assert.strictEqual(searchViewArch, serverData.views["animal,false,search"]); assert.deepEqual(searchViewFields, serverData.models.animal.fields); assert.strictEqual(searchViewId, false); assert.strictEqual(irFilters, undefined); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { // the rpc is done for fields assert.deepEqual(args.kwargs.views, [ [false, "toy"], [false, "search"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }, resModel: "animal", type: "toy", searchViewId: false, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerText, "Arch content (id=false)"); }); QUnit.test( "rendering with given arch, fields, searchViewId, searchViewArch, and searchViewFields", async function (assert) { assert.expect(6); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { irFilters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, false); assert.strictEqual(irFilters, undefined); }, }); const view = await makeView({ serverData, mockRPC: () => { throw new Error("no RPC expected"); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerText, "Specific arch content"); } ); QUnit.test( "rendering with given arch, fields, searchViewArch, and searchViewFields", async function (assert) { assert.expect(6); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { irFilters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, undefined); assert.strictEqual(irFilters, undefined); }, }); const view = await makeView({ serverData, mockRPC: () => { throw new Error("no RPC expected"); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerText, "Specific arch content"); } ); QUnit.test( "rendering with given arch, fields, searchViewId, searchViewArch, searchViewFields, and loadIrFilters='true'", async function (assert) { assert.expect(8); const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { irFilters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, false); assert.deepEqual(irFilters, serverData.models.animal.filters); }, }); const view = await makeView({ serverData, mockRPC: (_, args) => { // the rpc is done for fields assert.deepEqual(args.kwargs.views, [ [false, "toy"], [false, "search"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: true, toolbar: false, }); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerText, "Specific arch content"); } ); QUnit.test( "rendering with given arch, fields, searchViewId, searchViewArch, searchViewFields, irFilters, and loadIrFilters='true'", async function (assert) { assert.expect(6); const irFilters = [ { context: "{}", domain: "[]", id: 1, is_default: false, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ]; const ToyView = viewRegistry.get("toy"); patchWithCleanup(ToyView.prototype, { setup() { this._super(); const { irFilters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, undefined); assert.deepEqual(irFilters, irFilters); }, }); const view = await makeView({ serverData, mockRPC: () => { throw new Error("no RPC expected"); }, resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, irFilters, }); assert.hasClass(view.el, "o_toy_view"); assert.strictEqual(view.el.innerText, "Specific arch content"); } ); QUnit.test("can click on action-bound links -- 1", async (assert) => { assert.expect(5); const expectedAction = { action: { type: "ir.actions.client", tag: "someAction", }, options: {}, }; const fakeActionService = { name: "action", start() { return { doAction(action, options) { assert.deepEqual(action, expectedAction.action); assert.deepEqual(options, expectedAction.options); return Promise.resolve(true); }, }; }, }; serviceRegistry.add("action", fakeActionService, { force: true }); serverData.views["animal,1,toy"] = ` `; const mockRPC = (route) => { if (route.includes("setTheControl")) { assert.step(route); return { type: "ir.actions.client", tag: "someAction", }; } }; const toy = await makeView({ serverData, mockRPC, resModel: "animal", type: "toy", config: { views: [[1, "toy"]], }, }); assert.containsOnce(toy, "a"); await click(toy.el.querySelector("a")); assert.verifySteps(["/web/dataset/call_kw/animal/setTheControl"]); }); QUnit.test("can click on action-bound links -- 2", async (assert) => { assert.expect(3); const expectedAction = { action: "myLittleAction", options: { additionalContext: { somekey: "somevalue", }, }, }; const fakeActionService = { name: "action", start() { return { doAction(action, options) { assert.deepEqual(action, expectedAction.action); assert.deepEqual(options, expectedAction.options); return Promise.resolve(true); }, }; }, }; serviceRegistry.add("action", fakeActionService, { force: true }); serverData.views["animal,1,toy"] = ` `; const toy = await makeView({ serverData, resModel: "animal", type: "toy", config: { views: [[1, "toy"]], }, }); assert.containsOnce(toy, "a"); await click(toy.el.querySelector("a")); }); QUnit.test("can click on action-bound links -- 3", async (assert) => { assert.expect(3); const expectedAction = { action: { domain: [["field", "=", "val"]], name: "myTitle", res_id: 66, res_model: "animal", target: "current", type: "ir.actions.act_window", views: [[55, "toy"]], }, options: { additionalContext: { somekey: "somevalue", }, }, }; const fakeActionService = { name: "action", start() { return { doAction(action, options) { assert.deepEqual(action, expectedAction.action); assert.deepEqual(options, expectedAction.options); return Promise.resolve(true); }, }; }, }; serviceRegistry.add("action", fakeActionService, { force: true }); serverData.views["animal,1,toy"] = ` `; const toy = await makeView({ serverData, resModel: "animal", type: "toy", config: { views: [[1, "toy"]], }, }); assert.containsOnce(toy, "a"); await click(toy.el.querySelector("a")); }); QUnit.test("renders banner_route", async (assert) => { assert.expect(3); serverData.views["animal,1,toy"] = ` `; const mockRPC = (route) => { if (route === "/mybody/isacage") { assert.step(route); return { html: `
myBanner
` }; } }; const toy = await makeView({ serverData, mockRPC, resModel: "animal", type: "toy", config: { views: [[1, "toy"]], }, }); assert.verifySteps(["/mybody/isacage"]); assert.containsOnce(toy, ".setmybodyfree"); }); QUnit.test("renders banner_route with js and css assets", async (assert) => { assert.expect(7); serverData.views["animal,1,toy"] = ` `; const bannerArch = `