/** @odoo-module **/ import { makeTestEnv } from "@web/../tests/helpers/mock_env"; import { click, getFixture, makeDeferred, mount, nextTick, patchWithCleanup, mockTimeout, } from "@web/../tests/helpers/utils"; import { setupViewRegistries } 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"; import { Component, onWillStart, onWillUpdateProps, useState, xml } from "@odoo/owl"; import { CallbackRecorder } from "@web/webclient/actions/action_hook"; const serviceRegistry = registry.category("services"); const viewRegistry = registry.category("views"); let serverData; let target; 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 ToyController extends Component { setup() { this.class = "toy"; this.template = xml`${this.props.arch.outerHTML}`; } } ToyController.template = xml`
`; ToyController.components = { Banner: OnboardingBanner }; const toyView = { type: "toy", Controller: ToyController, }; class ToyControllerImp extends ToyController { setup() { super.setup(); this.class = "toy_imp"; } } viewRegistry.add("toy", toyView); viewRegistry.add("toy_imp", { ...toyView, Controller: ToyControllerImp }); setupViewRegistries(); const fakeActionService = { name: "action", start() { return { doAction() {}, }; }, }; serviceRegistry.add("action", fakeActionService, { force: true }); target = getFixture(); }); QUnit.module("View component"); //////////////////////////////////////////////////////////////////////////// // get_views //////////////////////////////////////////////////////////////////////////// QUnit.test("simple rendering", async function (assert) { assert.expect(10); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, false); }, }); const mockRPC = (_, args) => { assert.strictEqual(args.model, "animal"); assert.strictEqual(args.method, "get_views"); assert.deepEqual(args.kwargs.views, [[false, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view.o_view_controller"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,false,toy"] ); }); QUnit.test("rendering with given viewId", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const mockRPC = (_, args) => { assert.deepEqual(args.kwargs.views, [[1, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", viewId: 1, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,1,toy"] ); }); QUnit.test("rendering with given 'views' param", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const mockRPC = (_, args) => { assert.deepEqual(args.kwargs.views, [[1, "toy"]]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }; const config = { views: [[1, "toy"]], }; const env = await makeTestEnv({ serverData, mockRPC, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,1,toy"] ); }); QUnit.test( "rendering with given 'views' param not containing view id", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, false); }, }); const mockRPC = (_, args) => { assert.deepEqual(args.kwargs.views, [ [false, "other"], [false, "toy"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }; const config = { views: [[false, "other"]], }; const env = await makeTestEnv({ serverData, mockRPC, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,false,toy"] ); } ); QUnit.test("viewId defined as prop and in 'views' prop", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,1,toy"]); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, 1); }, }); const mockRPC = (_, args) => { assert.deepEqual(args.kwargs.views, [ [1, "toy"], [false, "other"], ]); assert.deepEqual(args.kwargs.options, { action_id: false, load_filters: false, toolbar: false, }); }; const config = { views: [ [3, "toy"], [false, "other"], ], }; const env = await makeTestEnv({ serverData, mockRPC, config }); const props = { resModel: "animal", type: "toy", viewId: 1, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,1,toy"] ); }); QUnit.test("rendering with given arch and fields", async function (assert) { assert.expect(6); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, `Specific arch content`); assert.deepEqual(fields, {}); assert.strictEqual(info.actionMenus, undefined); assert.strictEqual(this.env.config.viewId, undefined); }, }); const mockRPC = () => { throw new Error("no RPC expected"); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, `Specific arch content` ); }); QUnit.test("rendering with loadActionMenus='true'", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, serverData.views["animal,false,toy"]); assert.deepEqual(fields, {}); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, false); }, }); const 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, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", loadActionMenus: true, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, serverData.views["animal,false,toy"] ); }); QUnit.test( "rendering with given arch, fields, and loadActionMenus='true'", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, `Specific arch content`); assert.deepEqual(fields, {}); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, false); }, }); const 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, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, loadActionMenus: true, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, `Specific arch content` ); } ); QUnit.test( "rendering with given arch, fields, actionMenus, and loadActionMenus='true'", async function (assert) { assert.expect(6); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; assert.strictEqual(arch.outerHTML, `Specific arch content`); assert.deepEqual(fields, {}); assert.deepEqual(info.actionMenus, {}); assert.strictEqual(this.env.config.viewId, undefined); }, }); const mockRPC = () => { throw new Error("no RPC expected"); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, loadActionMenus: true, actionMenus: {}, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view.toy").innerHTML, `Specific arch content` ); } ); QUnit.test("rendering with given searchViewId", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); 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 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, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", searchViewId: false, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view").innerText, "Arch content (id=false)" ); }); QUnit.test( "rendering with given arch, fields, searchViewId, searchViewArch, and searchViewFields", async function (assert) { assert.expect(6); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, false); assert.strictEqual(irFilters, undefined); }, }); const mockRPC = () => { throw new Error("no RPC expected"); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view").innerText, "Specific arch content" ); } ); QUnit.test( "rendering with given arch, fields, searchViewArch, and searchViewFields", async function (assert) { assert.expect(6); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, undefined); assert.strictEqual(irFilters, undefined); }, }); const mockRPC = () => { throw new Error("no RPC expected"); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view").innerText, "Specific arch content" ); } ); QUnit.test( "rendering with given arch, fields, searchViewId, searchViewArch, searchViewFields, and loadIrFilters='true'", async function (assert) { assert.expect(8); const ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); 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 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, }); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view").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 ToyController = viewRegistry.get("toy").Controller; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; assert.strictEqual(searchViewArch, ``); assert.deepEqual(searchViewFields, {}); assert.strictEqual(searchViewId, undefined); assert.deepEqual(irFilters, irFilters); }, }); const mockRPC = () => { throw new Error("no RPC expected"); }; const env = await makeTestEnv({ serverData, mockRPC }); const props = { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, irFilters, }; await mount(View, target, { env, props }); assert.containsOnce(target, ".o_toy_view"); assert.strictEqual( target.querySelector(".o_toy_view").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"] = ` link `; const mockRPC = (route) => { if (route.includes("setTheControl")) { assert.step(route); return { type: "ir.actions.client", tag: "someAction", }; } }; const config = { views: [[1, "toy"]], }; const env = await makeTestEnv({ serverData, mockRPC, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, "a"); await click(target.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"] = ` link `; const config = { views: [[1, "toy"]], }; const env = await makeTestEnv({ serverData, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, "a"); await click(target.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"] = ` link `; const config = { views: [[1, "toy"]], }; const env = await makeTestEnv({ serverData, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.containsOnce(target, "a"); await click(target.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 config = { views: [[1, "toy"]], }; const env = await makeTestEnv({ serverData, mockRPC, config }); const props = { resModel: "animal", type: "toy", }; await mount(View, target, { env, props }); assert.verifySteps(["/mybody/isacage"]); assert.containsOnce(target, ".setmybodyfree"); }); QUnit.test("renders banner_route with js and css assets", async (assert) => { assert.expect(7); serverData.views["animal,1,toy"] = ` `; const bannerArch = `