import { before, expect, test } from "@odoo/hoot"; import { click, queryOne } from "@odoo/hoot-dom"; import { animationFrame } from "@odoo/hoot-mock"; import { Component, onWillStart, onWillUpdateProps, useState, xml } from "@odoo/owl"; import { defineModels, expectMarkup, fields, makeMockEnv, mockService, models, mountWithCleanup, onRpc, patchWithCleanup, serverState, } from "@web/../tests/web_test_helpers"; import { registry } from "@web/core/registry"; import { pick } from "@web/core/utils/objects"; import { View } from "@web/views/view"; import { CallbackRecorder } from "@web/search/action_hook"; const viewRegistry = registry.category("views"); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { this.class = "toy"; this.template = xml`${this.props.arch.outerHTML}`; } } const toyView = { type: "toy", Controller: ToyController, }; class ToyControllerImp extends ToyController { setup() { super.setup(); this.class = "toy_imp"; } } before(() => { patchWithCleanup(serverState.view_info, { toy: { multi_record: true, display_name: "Toy", icon: "fab fa-android" }, }); viewRegistry.add("toy", toyView); viewRegistry.add("toy_imp", { ...toyView, Controller: ToyControllerImp }); }); class Animal extends models.Model { birthday = fields.Date(); type = fields.Selection({ selection: [ ["omnivorous", "Omnivorous"], ["herbivorous", "Herbivorous"], ["carnivorous", "Carnivorous"], ], default: "red", }); _views = { toy: /* xml */ `Arch content (id=false)`, "toy,1": /* xml */ `Arch content (id=1)`, "toy,2": /* xml */ `Arch content (id=2)`, other: /* xml */ ``, search: /* xml */ ``, "search,1": /* xml */ ` `, }; _filters = [ { context: "{}", domain: "[('animal', 'ilike', 'o')]", id: 7, is_default: true, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ]; } defineModels([Animal]); //////////////////////////////////////////////////////////////////////////// // get_views //////////////////////////////////////////////////////////////////////////// test("simple rendering", async function () { expect.assertions(9); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=false)`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(false); }, }); onRpc("get_views", ({ model, kwargs }) => { expect(model).toBe("animal"); expect(kwargs.views).toEqual([[false, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy" } }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=false)`); }); test("rendering with given viewId", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=1)`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(1); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[1, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 1 } }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=1)`); }); test("rendering with given 'views' param", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=1)`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(1); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[1, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await makeMockEnv({ config: { views: [[1, "toy"]] } }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy" } }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=1)`); }); test("rendering with given 'views' param not containing view id", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=false)`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(false); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([ [false, "other"], [false, "toy"], ]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await makeMockEnv({ config: { views: [[false, "other"]] } }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy" } }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=false)`); }); test("viewId defined as prop and in 'views' prop", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=1)`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(1); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([ [1, "toy"], [false, "other"], ]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await makeMockEnv({ config: { views: [ [3, "toy"], [false, "other"], ], }, }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 1 } }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=1)`); }); test("rendering with given arch and fields", async function () { expect.assertions(6); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Specific arch content`); expect(fields).toEqual({}); expect(info.actionMenus).toBe(undefined); expect(this.env.config.viewId).toBe(undefined); }, }); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with loadActionMenus='true'", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Arch content (id=false)`); expect(fields).toEqual({}); expect(info.actionMenus).toEqual({}); expect(this.env.config.viewId).toBe(false); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[false, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: true, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", loadActionMenus: true }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=false)`); }); test("rendering with given arch, fields, and loadActionMenus='true'", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Specific arch content`); expect(fields).toEqual({}); expect(info.actionMenus).toEqual({}); expect(this.env.config.viewId).toBe(false); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[false, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: true, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, loadActionMenus: true, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with given arch, fields, actionMenus, and loadActionMenus='true'", async function () { expect.assertions(6); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { arch, fields, info } = this.props; expectMarkup(arch.outerHTML).toBe(`Specific arch content`); expect(fields).toEqual({}); expect(info.actionMenus).toEqual({}); expect(this.env.config.viewId).toBe(undefined); }, }); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, loadActionMenus: true, actionMenus: {}, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with given searchViewId", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; expect(searchViewArch).toBe(``); expect(searchViewFields).toEqual({ id: { string: "Id", readonly: true, required: false, searchable: true, sortable: true, store: true, groupable: true, aggregator: "sum", type: "integer", name: "id", }, display_name: { string: "Display name", readonly: true, required: false, searchable: true, sortable: true, store: false, groupable: true, type: "char", compute: "_compute_display_name", name: "display_name", }, create_date: { string: "Created on", readonly: true, required: false, searchable: true, sortable: true, store: true, groupable: true, type: "datetime", name: "create_date", }, write_date: { string: "Last Modified on", readonly: true, required: false, searchable: true, sortable: true, store: true, groupable: true, type: "datetime", name: "write_date", }, birthday: { string: "Birthday", readonly: false, required: false, searchable: true, sortable: true, store: true, groupable: true, type: "date", name: "birthday", }, type: { string: "Type", readonly: false, required: false, searchable: true, sortable: true, store: true, groupable: true, type: "selection", selection: [ ["omnivorous", "Omnivorous"], ["herbivorous", "Herbivorous"], ["carnivorous", "Carnivorous"], ], default: "red", name: "type", }, }); expect(searchViewId).toBe(false); expect(irFilters).toBe(undefined); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([ [false, "toy"], [false, "search"], ]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", searchViewId: false }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Arch content (id=false)`); }); test("rendering with given arch, fields, searchViewId, searchViewArch, and searchViewFields", async function () { expect.assertions(6); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; expect(searchViewArch).toBe(``); expect(searchViewFields).toEqual({}); expect(searchViewId).toBe(false); expect(irFilters).toBe(undefined); }, }); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with given arch, fields, searchViewArch, and searchViewFields", async function () { expect.assertions(6); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; expect(searchViewArch).toBe(``); expect(searchViewFields).toEqual({}); expect(searchViewId).toBe(undefined); expect(irFilters).toBe(undefined); }, }); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with given arch, fields, searchViewId, searchViewArch, searchViewFields, and loadIrFilters='true'", async function () { expect.assertions(8); patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters, searchViewArch, searchViewFields, searchViewId } = this.props.info; expect(searchViewArch).toBe(``); expect(searchViewFields).toEqual({}); expect(searchViewId).toBe(false); expect(irFilters).toEqual([ { context: "{}", domain: "[('animal', 'ilike', 'o')]", id: 7, is_default: true, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ]); }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([ [false, "toy"], [false, "search"], ]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: true, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewId: false, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("rendering with given arch, fields, searchViewId, searchViewArch, searchViewFields, irFilters, and loadIrFilters='true'", async function () { expect.assertions(6); const irFilters = [ { context: "{}", domain: "[]", id: 1, is_default: false, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ]; patchWithCleanup(ToyController.prototype, { setup() { super.setup(); const { irFilters: filters, searchViewArch, searchViewFields, searchViewId, } = this.props.info; expect(searchViewArch).toBe(``); expect(searchViewFields).toEqual({}); expect(searchViewId).toBe(undefined); expect(filters).toBe(irFilters); // irFilters is passed as it is without transformation -> we can use toBe instead of toEqual to avoid a warning }, }); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content`, fields: {}, searchViewArch: ``, searchViewFields: {}, loadIrFilters: true, irFilters, }, }); expect(".o_toy_view.o_view_controller").toHaveCount(1); expect(".o_toy_view.toy").toHaveInnerHTML(`Specific arch content`); }); test("can click on action-bound links -- 1", async () => { expect.assertions(4); mockService("action", { async doAction(actionRequest, options) { expect(actionRequest).toEqual({ type: "ir.actions.client", tag: "someAction", }); expect(options).toEqual({}); }, }); Animal._views[["toy", 1]] = /* xml */ ` link `; onRpc("setTheControl", () => { expect.step("root called"); return { type: "ir.actions.client", tag: "someAction" }; }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 1 } }); expect("a").toHaveCount(1); await click("a"); await animationFrame(); expect.verifySteps(["root called"]); }); test("can click on action-bound links -- 2", async () => { expect.assertions(3); mockService("action", { async doAction(actionRequest, options) { expect(actionRequest).toBe("myLittleAction"); expect(options).toEqual({ additionalContext: { somekey: "somevalue", }, }); }, }); Animal._views[["toy", 1]] = /* xml */ ` link `; await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 1 } }); expect("a").toHaveCount(1); await click("a"); await animationFrame(); }); test("can click on action-bound links -- 3", async () => { expect.assertions(3); mockService("action", { async doAction(actionRequest, options) { expect(actionRequest).toEqual({ domain: [["field", "=", "val"]], name: "myTitle", res_id: 66, res_model: "animal", target: "current", type: "ir.actions.act_window", views: [[55, "toy"]], }); expect(options).toEqual({ additionalContext: { somekey: "somevalue", }, }); }, }); Animal._views[["toy", 1]] = /* xml */ ` link `; await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 1 } }); expect("a").toHaveCount(1); await click("a"); await animationFrame(); }); //////////////////////////////////////////////////////////////////////////// // js_class //////////////////////////////////////////////////////////////////////////// test("rendering with given jsClass", async function () { expect.assertions(4); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[false, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", jsClass: "toy_imp" }, }); expect(".o_toy_view.toy_imp").toHaveCount(1); expect(".o_toy_view.toy_imp").toHaveText("Arch content (id=false)"); }); test("rendering with loaded arch attribute 'js_class'", async function () { expect.assertions(4); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[2, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", viewId: 2 } }); expect(".o_toy_view.toy_imp").toHaveCount(1); expect(".o_toy_view.toy_imp").toHaveText("Arch content (id=2)"); }); test("rendering with given arch attribute 'js_class'", async function () { expect.assertions(2); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", arch: `Specific arch content for specific class`, fields: {}, }, }); expect(".o_toy_view.toy_imp").toHaveCount(1); expect(".o_toy_view.toy_imp").toHaveText("Specific arch content for specific class"); }); test("rendering with loaded arch attribute 'js_class' and given jsClass", async function () { expect.assertions(3); viewRegistry.add("toy_2", { type: "toy", Controller: class extends Component { static props = ["*"]; static template = xml`
`; static type = "toy"; }, }); onRpc("get_views", ({ kwargs }) => { expect(kwargs.views).toEqual([[2, "toy"]]); expect(pick(kwargs.options, "action_id", "load_filters", "toolbar")).toEqual({ action_id: false, load_filters: false, toolbar: false, }); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", jsClass: "toy_2", viewId: 2, }, }); expect(".o_toy_view.toy_imp").toHaveCount(1); }); test("rendering with given arch attribute 'js_class' and given jsClass", async function () { expect.assertions(1); viewRegistry.add( "toy_2", { type: "toy", Controller: class extends Component { static props = ["*"]; static template = xml`
`; static type = "toy"; }, }, { force: true } ); onRpc("get_views", () => { throw new Error("no get_views expected"); }); await mountWithCleanup(View, { props: { resModel: "animal", type: "toy", jsClass: "toy_2", arch: ``, fields: {}, }, }); expect(".o_toy_view.toy_imp").toHaveCount(1); }); //////////////////////////////////////////////////////////////////////////// // props validation //////////////////////////////////////////////////////////////////////////// test("'resModel' must be passed as prop", async function () { const props = {}; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`View props should have a "resModel" key`]); }); test("'type' must be passed as prop", async function () { const props = { resModel: "animal" }; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`View props should have a "type" key`]); }); test("'arch' cannot be passed as prop alone", async function () { const props = { resModel: "animal", type: "toy", arch: "" }; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`"arch" and "fields" props must be given together`]); }); test("'fields' cannot be passed as prop alone", async function () { const props = { resModel: "animal", type: "toy", fields: {} }; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`"arch" and "fields" props must be given together`]); }); test("'searchViewArch' cannot be passed as prop alone", async function () { const props = { resModel: "animal", type: "toy", searchViewArch: "" }; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`"searchViewArch" and "searchViewFields" props must be given together`]); }); test("'searchViewFields' cannot be passed as prop alone", async function () { const props = { resModel: "animal", type: "toy", searchViewFields: {} }; try { await mountWithCleanup(View, { props }); } catch (error) { expect.step(error.message); } expect.verifySteps([`"searchViewArch" and "searchViewFields" props must be given together`]); }); //////////////////////////////////////////////////////////////////////////// // props //////////////////////////////////////////////////////////////////////////// test("search query props are passed as props to concrete view (default search arch)", async function () { expect.assertions(4); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { const { context, domain, groupBy, orderBy } = this.props; expect(context).toEqual({ lang: "en", tz: "taht", uid: 7, key: "val", allowed_company_ids: [1], }); expect(domain).toEqual([[0, "=", 1]]); expect(groupBy).toEqual(["birthday"]); expect(orderBy).toEqual([{ name: "bar", asc: true }]); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", domain: [[0, "=", 1]], groupBy: ["birthday"], context: { key: "val" }, orderBy: [{ name: "bar", asc: true }], }; await mountWithCleanup(View, { props }); }); test("non empty prop 'noContentHelp'", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.info.noContentHelp).toBe("
Help
"); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", noContentHelp: "
Help
", }; await mountWithCleanup(View, { props }); }); test("useSampleModel false by default", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.useSampleModel).toBe(false); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy" }; await mountWithCleanup(View, { props }); }); test("sample='1' on arch", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.useSampleModel).toBe(true); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", arch: ``, fields: {}, }; await mountWithCleanup(View, { props }); }); test("sample='0' on arch and useSampleModel=true", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.useSampleModel).toBe(true); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", useSampleModel: true, arch: ``, fields: {}, }; await mountWithCleanup(View, { props }); }); test("sample='1' on arch and useSampleModel=false", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.useSampleModel).toBe(false); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", useSampleModel: false, arch: ``, fields: {}, }; await mountWithCleanup(View, { props }); }); test("useSampleModel=true", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.useSampleModel).toBe(true); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", useSampleModel: true }; await mountWithCleanup(View, { props }); }); test("rendering with given prop", async function () { expect.assertions(1); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.props.specificProp).toBe("specificProp"); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { resModel: "animal", type: "toy", specificProp: "specificProp" }; await mountWithCleanup(View, { props }); }); test("search query props are passed as props to concrete view (specific search arch)", async function () { expect.assertions(4); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { const { context, domain, groupBy, orderBy } = this.props; expect(context).toEqual({ lang: "en", tz: "taht", uid: 7, allowed_company_ids: [1], }); expect(domain).toEqual(["&", [0, "=", 1], [1, "=", 1]]); expect(groupBy).toEqual(["display_name"]); expect(orderBy).toEqual([{ name: "bar", asc: true }]); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { type: "toy", resModel: "animal", searchViewId: 1, domain: [[0, "=", 1]], groupBy: ["birthday"], context: { search_default_filter: 1, search_default_group_by: 1 }, orderBy: [{ name: "bar", asc: true }], }; await mountWithCleanup(View, { props }); }); test("multiple ways to pass classes for styling", async () => { const props = { resModel: "animal", type: "toy", className: "o_custom_class_from_props_1 o_custom_class_from_props_2", arch: ` `, fields: {}, }; await mountWithCleanup(View, { props }); const view = queryOne(".o_toy_view"); expect(view).toHaveClass("o_toy_imp_view", { message: "should have the class from js_class attribute", }); expect(view).toHaveClass("o_custom_class_from_props_1", { message: "should have the class from props", }); expect(view).toHaveClass("o_custom_class_from_props_2", { message: "should have the class from props", }); expect(view).toHaveClass("o_custom_class_from_arch_1", { message: "should have the class from arch", }); expect(view).toHaveClass("o_custom_class_from_arch_2", { message: "should have the class from arch", }); }); test("callback recorders are moved from props to subenv", async () => { expect.assertions(5); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { expect(this.env.__getGlobalState__).toBeInstanceOf(CallbackRecorder); // put in env by View expect(this.env.__getContext__).toBeInstanceOf(CallbackRecorder); // put in env by View expect(this.env.__getLocalState__).toBe(null); // set by View expect(this.env.__beforeLeave__).toBe(null); // set by View expect(this.env.__getOrderBy__).toBeInstanceOf(CallbackRecorder); // put in env by WithSearch } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const props = { type: "toy", resModel: "animal", __getGlobalState__: new CallbackRecorder(), __getContext__: new CallbackRecorder(), }; await mountWithCleanup(View, { props }); }); //////////////////////////////////////////////////////////////////////////// // update props //////////////////////////////////////////////////////////////////////////// test("react to prop 'domain' changes", async function () { expect.assertions(2); class ToyController extends Component { static props = ["*"]; static template = xml`
`; setup() { onWillStart(() => { expect(this.props.domain).toEqual([["type", "=", "carnivorous"]]); }); onWillUpdateProps((nextProps) => { expect(nextProps.domain).toEqual([["type", "=", "herbivorous"]]); }); } } viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); class Parent extends Component { static props = ["*"]; static template = xml``; static components = { View }; setup() { this.state = useState({ type: "toy", resModel: "animal", domain: [["type", "=", "carnivorous"]], }); } } const parent = await mountWithCleanup(Parent); parent.state.domain = [["type", "=", "herbivorous"]]; await animationFrame(); });