/** @odoo-module */ import { expect, test } from "@odoo/hoot"; import { queryAll, queryAllTexts, queryFirst, queryOne, queryText } from "@odoo/hoot-dom"; import { Deferred, animationFrame, mockDate } from "@odoo/hoot-mock"; import { markup } from "@odoo/owl"; import { contains, defineModels, editFavoriteName, fields, findComponent, getDropdownMenu, getFacetTexts, getService, mockService, models, mountView, mountWithCleanup, onRpc, patchWithCleanup, saveFavorite, toggleMenu, toggleMenuItem, toggleMenuItemOption, toggleSaveFavorite, toggleSearchBarMenu, } from "@web/../tests/web_test_helpers"; import { _t } from "@web/core/l10n/translation"; import { download } from "@web/core/network/download"; import { PivotController } from "@web/views/pivot/pivot_controller"; import { WebClient } from "@web/webclient/webclient"; function getCurrentValues() { return queryAllTexts(".o_pivot_cell_value div").join(); } async function removeFacet() { await contains("div.o_searchview_facet:eq(0) .o_facet_remove").click(); } class Partner extends models.Model { _name = "partner"; foo = fields.Integer({ groupable: false }); bar = fields.Boolean({ string: "bar" }); date = fields.Date(); product_id = fields.Many2one({ relation: "product" }); other_product_id = fields.Many2one({ relation: "product" }); non_stored_m2o = fields.Many2one({ relation: "product", groupable: false }); customer = fields.Many2one({ relation: "customer" }); computed_field = fields.Integer({ string: "Computed and not stored", compute: () => 1, aggregator: "sum", groupable: false, }); company_type = fields.Selection({ selection: [ ["company", "Company"], ["individual", "individual"], ], }); price_nonaggregable = fields.Monetary({ string: "Price non-aggregable", aggregator: undefined, currency_field: this.currency_id, groupable: false, }); reference = fields.Reference({ string: "Reference", selection: [ ["product", "Product"], ["customer", "Customer"], ], aggregator: "count_distinct", }); parent_id = fields.Many2one({ relation: "partner", groupable: false }); properties = fields.Properties({ definition_record: "parent_id", definition_record_field: "properties_definition", }); properties_definition = fields.PropertiesDefinition({ string: "Properties", groupable: false }); display_name = fields.Char({ groupable: false }); create_date = fields.Datetime({ groupable: false }); write_date = fields.Datetime({ groupable: false }); _records = [ { id: 1, foo: 12, bar: true, date: "2016-12-14", product_id: 37, customer: 1, company_type: "company", reference: "product,37", properties_definition: [ { name: "my_char", string: "My Char", type: "char", }, ], }, { id: 2, foo: 1, bar: true, date: "2016-10-26", product_id: 41, customer: 2, company_type: "individual", reference: "product,41", parent_id: 1, properties: { my_char: "aaa", }, }, { id: 3, foo: 17, bar: true, date: "2016-12-15", product_id: 41, customer: 2, company_type: "company", reference: "customer,1", parent_id: 1, properties: { my_char: "bbb", }, }, { id: 4, foo: 2, bar: false, date: "2016-04-11", product_id: 41, customer: 1, company_type: "individual", reference: "customer,2", }, ]; } class Product extends models.Model { _name = "product"; name = fields.Char({ string: "Product Name" }); _records = [ { id: 37, name: "xphone", }, { id: 41, name: "xpad", }, ]; } class Customer extends models.Model { _name = "customer"; name = fields.Char({ string: "Customer Name" }); _records = [ { id: 1, name: "First", }, { id: 2, name: "Second", }, ]; } class User extends models.Model { _name = "res.users"; name = fields.Char(); has_group() { return true; } } defineModels([Partner, Product, Customer, User]); test('pivot view without "string" attribute', async () => { const view = await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); const model = findComponent(view, (c) => c instanceof PivotController).model; // this is important for export functionality. expect(model.metaData.title.toString()).toBe(_t("Untitled")); }); test('pivot view with "class" attribute', async () => { await mountView({ type: "pivot", resModel: "partner", arch: ``, }); expect(".o_pivot_view").toHaveClass("foobar-class"); }); test("simple pivot rendering", async () => { expect.assertions(4); onRpc("read_group", ({ kwargs }) => { expect(kwargs.lazy).toBe(false); }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot_view").toHaveClass("o_view_controller"); expect("table").toHaveClass("o_enable_linking"); expect("td.o_pivot_cell_value:contains(32)").toHaveCount(1); }); test("all measures should be displayed with a pivot_measures context", async () => { Partner._fields.bouh = fields.Integer({ string: "bouh", aggregator: "sum" }); await mountView({ type: "pivot", resModel: "partner", context: { pivot_measures: ["foo"] }, arch: ` `, }); await contains("button:contains(Measures)").click(); expect(".o_popover.popover.o-dropdown--menu.dropdown-menu").toHaveCount(1); const measures = queryAllTexts(".o-dropdown-item"); expect(measures).toEqual(["bouh", "Computed and not stored", "Foo", "Count"]); }); test("pivot rendering with widget", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("td.o_pivot_cell_value:contains(32:00)").toHaveCount(1); }); test("pivot rendering with string attribute on field", async () => { Partner._fields.foo = fields.Integer(); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); const toggler = ".o_pivot_buttons button.dropdown-toggle"; await contains(toggler).click(); expect(".o-dropdown-item:first").toHaveText("BAR"); expect(".o_pivot_measure_row").toHaveText("BAR"); }); test("Pivot with integer row group by with 0 as header", async () => { Partner._records[0].foo = 0; Partner._records[1].foo = 0; Partner._records[2].foo = 0; Partner._records[3].foo = 0; await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot table tr td.o_pivot_cell_value").toHaveCount(2); expect(".o_pivot table tbody tr:eq(0) th:eq(0)").toHaveText("Total"); expect(".o_pivot table tbody tr:eq(0) td:eq(0)").toHaveText("0"); }); test("Pivot with integer col group by with 0 as header", async () => { Partner._records[0].foo = 0; Partner._records[1].foo = 0; Partner._records[2].foo = 0; Partner._records[3].foo = 0; await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot table thead tr:eq(1) th").toHaveText("0"); }); test("pivot rendering with string attribute on non stored field", async () => { Partner._fields.fubar = fields.Integer({ aggregator: "sum", store: false, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot table thead tr:eq(1) th").toHaveText("fubar"); }); test("pivot rendering with invisible attribute on field", async () => { // when invisible, a field should neither be an active measure nor be a selectable measure Partner._fields.foo = fields.Integer(); Partner._fields.foo2 = fields.Integer(); Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", compute: () => 1, aggregator: null, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); // there should be only one displayed measure as the other one is invisible expect(".o_pivot_measure_row").toHaveCount(1); await contains(".o_pivot_buttons button.dropdown-toggle").click(); // there should be only one measure besides count, as the other one is invisible expect(".dropdown-item").toHaveCount(2); expect(".dropdown-item:first").toHaveText("Foo"); // the invisible field souldn't be in the groupable fields neither await contains(".o_pivot_header_cell_closed").click(); expect('.o-dropdown--menu a[data-field="foo2"]').toHaveCount(0); }); test("group headers should have a tooltip", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAll("tbody .o_pivot_header_cell_closed").at(0).dataset.tooltip).toBe("Date"); expect(queryAll("thead .o_pivot_header_cell_closed").at(1).dataset.tooltip).toBe("Product"); }); test("pivot view add computed fields explicitly defined as measure", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_buttons button.dropdown-toggle").click(); expect(".dropdown-item:contains(Computed and not stored)").toHaveCount(1); expect(".o_pivot_measure_row").toHaveText("Computed and not stored"); }); test("pivot view do not add number field without aggregator", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_buttons button.dropdown-toggle").click(); expect(".dropdown-item:contains(Price non-aggregable)").toHaveCount(0); }); test("clicking on a cell triggers a doAction", async () => { expect.assertions(2); Partner._views["form,2"] = `
`; Partner._views["list,false"] = ``; Partner._views["kanban,5"] = ``; mockService("action", { doAction(action) { expect(action).toEqual({ context: { lang: "en", tz: "taht", someKey: true, uid: 7, allowed_company_ids: [1], }, domain: [["product_id", "=", 37]], name: "Partners", res_model: "partner", target: "current", type: "ir.actions.act_window", view_mode: "list", views: [ [false, "list"], [2, "form"], ], }); return Promise.resolve(true); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, context: { someKey: true, search_default_test: 3 }, config: { views: [ [2, "form"], [5, "kanban"], [false, "list"], [false, "pivot"], ], }, }); expect("table").toHaveClass("o_enable_linking"); await contains(".o_pivot_cell_value:eq(1)").click(); // should trigger a do_action }); test.tags("desktop"); test("row and column are highlighted when hovering a cell", async () => { expect.assertions(11); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); // check row highlighting expect("table").toHaveClass("table-hover"); // check column highlighting // hover third measure await contains("th.o_pivot_measure_row:nth-of-type(3)").hover(); expect(".o_cell_hover").toHaveCount(3); for (var i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(3)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); // hover second cell, second row await contains("tbody tr:nth-of-type(1) td:nth-of-type(2)").hover(); expect(".o_cell_hover").toHaveCount(3); for (i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(2)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); }); test.tags("desktop"); test("columns are highlighted when hovering a measure", async () => { expect.assertions(15); mockDate("2016-12-20T1:00:00"); Partner._records[0].date = "2016-11-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: true }, }); await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); // hover Count in first group await contains("th.o_pivot_measure_row:nth-of-type(1)").hover(); expect(".o_cell_hover").toHaveCount(3); for (let i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(1)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); // hover Count in second group await contains("th.o_pivot_measure_row:nth-of-type(2)").hover(); expect(".o_cell_hover").toHaveCount(3); for (let i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(4)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); // hover Count in total column await contains("th.o_pivot_measure_row:nth-of-type(3)").hover(); expect(".o_cell_hover").toHaveCount(3); for (let i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(7)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); }); test.tags("desktop"); test("columns are highlighted when hovering an origin (comparison mode)", async () => { expect.assertions(5); mockDate("2016-12-20T1:00:00"); Partner._records[0].date = "2016-11-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: true }, }); await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); // hover the second origin in second group await contains("th.o_pivot_origin_row:nth-of-type(5)").hover(); expect(".o_cell_hover").toHaveCount(3); for (let i = 1; i <= 3; i++) { expect(`tbody tr:nth-of-type(${i}) td:nth-of-type(5)`).toHaveClass("o_cell_hover"); } await contains(".o_pivot_buttons button.dropdown-toggle").hover(); expect(".o_cell_hover").toHaveCount(0); }); test('pivot view with disable_linking="True"', async () => { mockService("action", { doAction() { throw new Error("should not execute an action"); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("table").not.toHaveClass("o_enable_linking"); expect(".o_pivot_cell_value").toHaveCount(1); await contains(".o_pivot_cell_value").click(); // should not trigger a do_action }); test('clicking on the "Total" cell with time range activated', async () => { expect.assertions(2); mockDate("2016-12-20T1:00:00"); mockService("action", { doAction(action) { expect(action.domain).toEqual([ "&", ["date", ">=", "2016-12-01"], ["date", "<=", "2016-12-31"], ]); return Promise.resolve(true); }, }); await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, context: { search_default_date_filter: true }, }); expect("table").toHaveClass("o_enable_linking"); await contains(".o_pivot_cell_value").click(); }); test('clicking on a fake cell value ("empty group") in comparison mode', async () => { expect.assertions(3); mockDate("2016-12-20T1:00:00"); Partner._records[0].date = "2016-11-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; const expectedDomains = [ ["&", ["date", ">=", "2016-12-01"], ["date", "<=", "2016-12-31"]], [[0, "=", 1]], ]; mockService("action", { doAction(action) { expect(action.domain).toEqual(expectedDomains.shift()); return Promise.resolve(true); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ``, searchViewArch: ` `, context: { search_default_date_filter: true }, }); await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); expect("table").toHaveClass("o_enable_linking"); // here we click on the group corresponding to Total/Total/This Month await contains(".o_pivot_cell_value:eq(1)").click(); // should trigger a do_action with appropriate domain // here we click on the group corresponding to xphone/Total/This Month await contains(".o_pivot_cell_value:eq(4)").click(); // should trigger a do_action with appropriate domain }); test("pivot view grouped by date field", async () => { expect.assertions(2); onRpc("read_group", ({ kwargs }) => { const wrongFields = kwargs.fields.filter((field) => { return !(field.split(":")[0] in Partner._fields); }); expect(wrongFields.length).toBe(0); }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); }); test("without measures, pivot view uses __count by default", async () => { Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", aggregator: null, }); Partner._fields.foo = fields.Integer({ aggregator: null }); expect.assertions(4); onRpc("read_group", ({ kwargs }) => { expect(kwargs.fields).toEqual(["__count"]); }); await mountView({ type: "pivot", resModel: "partner", arch: "", }); await contains(".o_pivot_buttons .dropdown-toggle").click(); const dropdownMenu = getDropdownMenu(".o_pivot_buttons button.dropdown-toggle"); expect(queryAll(".dropdown-item", { root: dropdownMenu })).toHaveCount(1); const measure = dropdownMenu.querySelector(".dropdown-item"); expect(measure).toHaveText("Count"); expect(measure).toHaveClass("selected"); }); test("pivot view grouped by many2one field", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot_header_cell_opened").toHaveCount(1); expect(".o_pivot_header_cell_closed:contains(xphone)").toHaveCount(1); expect(".o_pivot_header_cell_closed:contains(xpad)").toHaveCount(1); }); test("pivot view can be reloaded", async () => { let readGroupCount = 0; onRpc("read_group", () => { readGroupCount++; }); await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, }); expect("td.o_pivot_cell_value:contains(4)").toHaveCount(1); expect(readGroupCount).toBe(1); await toggleSearchBarMenu(); await toggleMenuItem("Some Filter"); expect("td.o_pivot_cell_value:contains(2)").toHaveCount(1); expect(readGroupCount).toBe(2); }); test.tags("desktop"); test("basic folding/unfolding", async () => { let rpcCount = 0; onRpc("read_group", () => { rpcCount++; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("tbody tr").toHaveCount(3); // click on the opened header to close it await contains(".o_pivot_header_cell_opened").click(); expect("tbody tr").toHaveCount(1); // click on closed header to open dropdown await contains("tbody .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu").toHaveCount(1); expect(queryAllTexts(".o-dropdown--menu .o-dropdown-item")).toEqual([ "Company type", "Customer", "Date", "Other product", "Product", "bar", ]); // open the Date sub dropdown await contains(".o-dropdown--menu .dropdown-toggle.o_menu_item").hover(); const subDropdownMenu = getDropdownMenu(".o-dropdown--menu .dropdown-toggle.o_menu_item"); expect(subDropdownMenu).toHaveText("Year\nQuarter\nMonth\nWeek\nDay"); await contains(queryOne(".dropdown-item:eq(2)", { root: subDropdownMenu })).click(); expect("tbody tr").toHaveCount(4); expect(rpcCount).toBe(3); }); test.tags("desktop"); test("more folding/unfolding", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); // open dropdown to zoom into first row await contains("tbody .o_pivot_header_cell_closed").click(); // click on date by day await contains(".o-dropdown--menu .dropdown-toggle").hover(); const subDropdownMenu = getDropdownMenu(".o-dropdown--menu .dropdown-toggle"); await contains(queryOne("span:nth-child(5)", { root: subDropdownMenu })).click(); // open dropdown to zoom into second row await contains("tbody th.o_pivot_header_cell_closed:eq(1)").click(); expect("tbody tr").toHaveCount(7); }); test("fold and unfold header group", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("thead tr").toHaveCount(3); // fold opened col group await contains("thead .o_pivot_header_cell_opened").click(); expect("thead tr").toHaveCount(2); // unfold it await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu span:nth-child(5)").click(); expect("thead tr").toHaveCount(3); }); test("unfold second header group", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("thead tr").toHaveCount(3); let values = ["12", "20", "32"]; expect(getCurrentValues()).toBe(values.join(",")); // unfold it await contains("thead .o_pivot_header_cell_closed:last-child").click(); await contains(".o-dropdown--menu span:nth-child(1)").click(); expect("thead tr").toHaveCount(4); values = ["12", "17", "3", "32"]; expect(getCurrentValues()).toBe(values.join(",")); }); test("pivot renders group dropdown same as search groupby dropdown if group bys are specified in search arch", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, // TOASK DAM: won´t appear in groupbymenu ? searchViewArch: ` `, }); // open group by dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(6); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(1); // click on closed header to open dropdown await contains("tbody tr:last-child .o_pivot_header_cell_closed").click(); expect(".dropdown-menu > .dropdown-item").toHaveCount(4); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(1); // check custom groupby selection has groupable fields only expect(".o_add_custom_group_menu option:not([disabled])").toHaveCount(6); const optionDescriptions = queryAllTexts(".o_add_custom_group_menu option:not([disabled])"); expect(optionDescriptions).toEqual([ "Company type", "Customer", "Date", "Other product", "Product", "bar", ]); }); test("pivot group dropdown sync with search groupby dropdown", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // open group by dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(5); // click on closed header to open dropdown await contains("tbody tr:last-child .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(3); // add a custom group in searchview groupby await toggleSearchBarMenu(); await contains(`.o_add_custom_group_menu`).select("company_type"); expect(".o-dropdown--menu .o_menu_item").toHaveCount(6); await contains("tbody tr:last-child .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(3); // add a custom group in pivot groupby await contains(`.o_add_custom_group_menu`).select("date"); // click on closed header to open groupby selection dropdown await contains("tbody tr:last-child .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(4); // applying custom groupby in pivot groupby dropdown will not update search dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(6); }); test("pivot custom groupby: grouping on date field use default interval month", async () => { expect.assertions(1); let checkReadGroup = false; onRpc("read_group", ({ kwargs }) => { if (checkReadGroup) { expect(kwargs.groupby).toEqual(["date:month"]); checkReadGroup = false; } }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // click on closed header to open dropdown and apply groupby on date field await contains("thead .o_pivot_header_cell_closed").click(); checkReadGroup = true; await contains(`.o_add_custom_group_menu`).select("date"); }); test("pivot groupby dropdown renders custom search at the end with separator", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // open group by dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(5); await contains(`.o_add_custom_group_menu`).select("company_type"); expect(".o-dropdown--menu .o_menu_item").toHaveCount(6); // click on closed header to open dropdown await contains("tbody .o_pivot_header_cell_closed:eq(1)").click(); let items = queryAll(".o_menu_item:not(select)"); expect(queryAllTexts(items)).toEqual(["bar", "product"]); expect(".o-dropdown--menu .dropdown-divider").toHaveCount(1); expect(items[items.length - 1].nextElementSibling).toHaveClass("dropdown-divider"); // add a custom group in pivot groupby await contains(`.o_add_custom_group_menu`).select("customer"); await contains("tbody .o_pivot_header_cell_closed:eq(1)").click(); items = queryAll(".o_menu_item:not(select)"); expect(queryAllTexts(items)).toEqual(["bar", "product", "Customer"]); expect(".o-dropdown--menu .dropdown-divider").toHaveCount(2); expect(items[items.length - 1].previousElementSibling).toHaveClass("dropdown-divider"); expect(items[items.length - 1].nextElementSibling).toHaveClass("dropdown-divider"); }); test("pivot view without group by specified in search arch", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); // open group by dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(3); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(1); // click on closed header to open dropdown await contains("tbody .o_pivot_header_cell_closed:eq(1)").click(); expect(".o-dropdown--menu .o_menu_item").toHaveCount(7); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(1); }); test("pivot view do not show custom group selection if there are no groupable fields", async () => { for (const fieldName of ["bar", "company_type", "customer", "date", "other_product_id"]) { delete Partner._fields[fieldName]; } // Keep product_id but make it ungroupable Partner._fields.product_id = fields.Many2one({ relation: "product", groupable: false, }); Partner._records = [ { id: 1, foo: 12, product_id: 37, }, ]; await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // open group by dropdown await toggleSearchBarMenu(); expect(".o-dropdown--menu .dropdown-item").toHaveCount(3); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(0); // click on closed header to open dropdown await contains("tbody .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu .dropdown-item").toHaveCount(1); expect(".o-dropdown--menu .o_add_custom_group_menu").toHaveCount(0); }); test("can toggle extra measure", async () => { let rpcCount = 0; onRpc(() => { rpcCount++; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); rpcCount = 0; expect(".o_pivot_cell_value").toHaveCount(3); await contains(".o_pivot_buttons button.dropdown-toggle").click(); expect(".dropdown-item:contains(Count)").not.toHaveClass("selected"); await contains(".dropdown-item:contains(Count):eq(0").click(); expect(".dropdown-item:contains(Count)").toHaveClass("selected"); expect(".o_pivot_cell_value").toHaveCount(6); expect(rpcCount).toBe(2); await contains(".dropdown-item:contains(Count):eq(0)").click(); expect(".dropdown-item:contains(Count):eq(0)").not.toHaveClass("selected"); expect(".o_pivot_cell_value").toHaveCount(3); expect(rpcCount).toBe(2); }); test("no content helper when no active measure", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ``, }); expect(".o_view_nocontent").toHaveCount(0); expect("table").toHaveCount(1); await contains(".o_pivot_buttons button.dropdown-toggle").click(); await contains(".dropdown-item:contains(Count):eq(0)").click(); expect(".o_view_nocontent").toHaveCount(1); expect("table").toHaveCount(0); }); test("no content helper when no data", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ``, searchViewArch: ` `, }); expect(".o_view_nocontent").toHaveCount(0); expect("table").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("Some Filter"); expect(".o_view_nocontent").toHaveCount(1); expect("table").toHaveCount(0); }); test("no content helper when no data, part 2", async () => { Partner._records = []; await mountView({ type: "pivot", resModel: "partner", arch: "", }); expect(".o_view_nocontent").toHaveCount(1); }); test.tags("desktop"); test("no content helper when no data, part 3", async () => { await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, context: { search_default_foo: 12345, }, }); expect(".o_searchview .o_searchview_facet").toHaveCount(1); expect(".o_view_nocontent").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("Some Filter"); expect(".o_searchview .o_searchview_facet").toHaveCount(2); expect(".o_view_nocontent").toHaveCount(1); await toggleMenuItem("Some Filter"); expect(".o_searchview .o_searchview_facet").toHaveCount(1); expect(".o_view_nocontent").toHaveCount(1); await contains(".o_facet_remove").click(); expect(".o_searchview .o_searchview_facet").toHaveCount(0); expect(".o_view_nocontent").toHaveCount(0); // tries to open a field selection menu, to make sure it was not // removed from the dom. await contains("tbody .o_pivot_header_cell_closed").click(); expect(".o-dropdown--menu").toHaveCount(1); }); test("tries to restore previous state after domain change", async () => { let rpcCount = 0; onRpc(() => { rpcCount++; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(".o_pivot_cell_value").toHaveCount(3); expect(".o_pivot_measure_row:contains(Foo)").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect("table").toHaveCount(0); rpcCount = 0; await removeFacet(); expect("table").toHaveCount(1); expect(rpcCount).toBe(2); expect(".o_pivot_cell_value").toHaveCount(3); expect(".o_pivot_measure_row:contains(Foo)").toHaveCount(1); }); test("can be grouped with the search view", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(".o_pivot_cell_value").toHaveCount(1); expect("tbody tr").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("Product"); expect(".o_pivot_cell_value").toHaveCount(3); expect("tbody tr").toHaveCount(3); }); test("can sort data in a column by clicking on header", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); let values = ["32", "12", "20"]; expect(getCurrentValues()).toBe(values.join(",")); await contains("th.o_pivot_measure_row").click(); values = ["32", "12", "20"]; expect(getCurrentValues()).toBe(values.join(",")); await contains("th.o_pivot_measure_row").click(); values = ["32", "20", "12"]; expect(getCurrentValues()).toBe(values.join(",")); }); test("can expand all rows", async () => { let nbReadGroups = 0; onRpc("read_group", () => { nbReadGroups++; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(nbReadGroups).toBe(2); expect(getCurrentValues()).toBe("32,12,20"); // expand on date:days, product await toggleSearchBarMenu(); await toggleMenuItem("Date"); await toggleMenuItemOption("Date", "Month"); nbReadGroups = 0; await toggleMenuItem("Product"); expect(nbReadGroups).toBe(3); expect("tbody tr").toHaveCount(8); // collapse the first two rows await contains("tbody .o_pivot_header_cell_opened:eq(2)").click(); await contains("tbody .o_pivot_header_cell_opened:eq(1)").click(); expect("tbody tr").toHaveCount(6); // expand all nbReadGroups = 0; await contains(".o_pivot_expand_button").click(); expect(nbReadGroups).toBe(3); expect("tbody tr").toHaveCount(8); }); test("expand all with a delay", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // expand on date:days, product await toggleSearchBarMenu(); await toggleMenuItem("Date"); await toggleMenuItemOption("Date", "Month"); await toggleMenuItem("Product"); expect("tbody tr").toHaveCount(8); // collapse the first two rows await contains("tbody .o_pivot_header_cell_opened:eq(2)").click(); await contains("tbody .o_pivot_header_cell_opened:eq(1)").click(); expect("tbody tr").toHaveCount(6); // expand all def = new Deferred(); await contains(".o_pivot_expand_button").click(); expect("tbody tr").toHaveCount(6); def.resolve(); await animationFrame(); expect("tbody tr").toHaveCount(8); }); test("can download a file", async () => { expect.assertions(1); patchWithCleanup(download, { _download: (options) => { expect(options.url).toBe("/web/pivot/export_xlsx"); return Promise.resolve(); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_download").click(); }); test("download a file with single measure, measure row displayed in table", async () => { expect.assertions(2); patchWithCleanup(download, { _download: ({ url, data }) => { data = JSON.parse(data.data); expect(url).toBe("/web/pivot/export_xlsx"); expect(data.measure_headers.length).toBe(4); return Promise.resolve(); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_download").click(); }); test("download button is disabled when there is no data", async () => { Partner._records = []; await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot_download").not.toBeEnabled(); }); test("correctly save measures and groupbys to favorite", async () => { expect.assertions(3); let expectedContext; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContext); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expectedContext = { group_by: [], pivot_column_groupby: ["date:day"], pivot_measures: ["foo"], pivot_row_groupby: [], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Fav1"); await saveFavorite(); // expand header on field customer await contains("thead .o_pivot_header_cell_closed:eq(1)").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); expectedContext = { group_by: [], pivot_column_groupby: ["date:day", "customer"], pivot_measures: ["foo"], pivot_row_groupby: [], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Fav2"); await saveFavorite(); // expand row on field product_id await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); expectedContext = { group_by: [], pivot_column_groupby: ["date:day", "customer"], pivot_measures: ["foo"], pivot_row_groupby: ["product_id"], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Fav3"); await saveFavorite(); }); test.tags("desktop"); test("correctly remove pivot_ keys from the context", async () => { expect.assertions(5); // in this test, we use "foo" as a measure Partner._fields.foo = fields.Integer({ groupable: false, }); Partner._fields.amount = fields.Float(); let expectedContext; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContext); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, context: { search_default_initial_context: 1, }, searchViewArch: ` `, }); // Unload the filter await removeFacet(); // remove previous favorite expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["foo"], pivot_row_groupby: ["product_id"], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("1"); await saveFavorite(); // Let's get rid of the rows groupBy await contains("tbody .o_pivot_header_cell_opened").click(); expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["foo"], pivot_row_groupby: [], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("2"); await saveFavorite(); // And now, get rid of both col and row groupby //await contains("tbody .o_pivot_header_cell_opened").click(); //It was already removed await contains("thead .o_pivot_header_cell_opened").click(); expectedContext = { group_by: [], pivot_column_groupby: [], pivot_measures: ["foo"], pivot_row_groupby: [], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("3"); await saveFavorite(); // Group row by product_id await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu span:nth-child(5)").click(); expectedContext = { group_by: [], pivot_column_groupby: [], pivot_measures: ["foo"], pivot_row_groupby: ["product_id"], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("4"); await saveFavorite(); // Group column by customer await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu span:nth-child(2)").click(); expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["foo"], pivot_row_groupby: ["product_id"], }; await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("5"); await saveFavorite(); }); test("Apply two groupby, and remove facet", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ` `; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], }); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("First"); // Apply both groupbys await toggleSearchBarMenu(); await toggleMenuItem("Product"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("xphone"); await toggleMenuItem("Bar"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("Yes"); // remove filter await removeFacet(); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("Yes"); }); test("Add a group by on the CP when a favorite already exists", async () => { Partner._views["pivot,false"] = ``; Partner._views["search,false"] = ` `; Partner._filters = [ { context: "{'pivot_row_groupby': ['date']}", domain: "[]", id: 7, is_default: true, name: "My favorite", sort: "[]", user_id: [2, "Mitchell Admin"], }, ]; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], }); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("April 2016"); // Apply BAR groupbys await toggleSearchBarMenu(); await toggleMenuItem("Bar"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("No"); // remove groupBy await toggleMenuItem("Bar"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("April 2016"); // remove all facets await removeFacet(); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("April 2016"); }); test("Adding a Favorite at anytime should modify the row/column groupby", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ``; Partner._filters = [ { user_id: [2, "Mitchell Admin"], name: "My favorite", id: 5, context: `{"pivot_row_groupby":["product_id"], "pivot_column_groupby": ["bar"]}`, sort: "[]", domain: "", is_default: false, model_id: "partner", action_id: false, }, ]; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], }); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("First"); expect(queryFirst("thead .o_pivot_header_cell_closed")).toHaveText("April 2016"); // activate the unique existing favorite await toggleSearchBarMenu(); await toggleMenuItem("my favorite"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("xphone"); expect(queryFirst("thead .o_pivot_header_cell_closed")).toHaveText("No"); // desactivate the unique existing favorite await toggleMenuItem("my favorite"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("xphone"); expect(queryFirst("thead .o_pivot_header_cell_closed")).toHaveText("No"); // Let's get rid of the rows and columns groupBy await contains("tbody .o_pivot_header_cell_opened").click(); await contains("thead .o_pivot_header_cell_opened").click(); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("Total"); expect(queryFirst("thead .o_pivot_header_cell_closed")).toHaveText("Total"); // activate AGAIN the unique existing favorite await toggleSearchBarMenu(); await toggleMenuItem("my favorite"); expect(queryFirst("tbody .o_pivot_header_cell_closed")).toHaveText("xphone"); expect(queryFirst("thead .o_pivot_header_cell_closed")).toHaveText("No"); }); test("Unload Filter, reset display, load another filter", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, context: { pivot_measures: ["foo"], pivot_column_groupby: ["customer"], pivot_row_groupby: ["product_id"], }, searchViewArch: ` `, }); // Check Columns expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect('thead tr:contains("First")').toHaveCount(1); expect('thead tr:contains("Second")').toHaveCount(1); // Check Rows expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect('tbody tr:contains("xphone")').toHaveCount(1); expect('tbody tr:contains("xpad")').toHaveCount(1); // Equivalent to unload the filter await toggleSearchBarMenu(); await toggleMenuItem("My fake favorite"); // collapse all headers await contains(".o_pivot_header_cell_opened:first-child").click(); await contains(".o_pivot_header_cell_opened").click(); // Check Columns expect("thead .o_pivot_header_cell_closed").toHaveCount(1); expect('thead tr:contains("First")').toHaveCount(0); expect('thead tr:contains("Second")').toHaveCount(0); // Check Rows expect("tbody .o_pivot_header_cell_closed").toHaveCount(1); expect('tbody tr:contains("xphone")').toHaveCount(0); expect('tbody tr:contains("xpad")').toHaveCount(0); // Equivalent to load another filter await removeFacet(); // remove previously saved favorite await toggleSearchBarMenu(); await toggleMenuItem("My fake favorite 2"); // Check Columns expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect('thead tr:contains("First")').toHaveCount(1); expect('thead tr:contains("Second")').toHaveCount(1); // Check Rows expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect('tbody tr:contains("xphone")').toHaveCount(1); expect('tbody tr:contains("xpad")').toHaveCount(1); }); test("Reload, group by columns, reload", async () => { expect.assertions(2); let expectedContext; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContext); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, }); // Set a column groupby await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); // Set a domain await toggleSearchBarMenu(); await toggleMenuItem("My Filter 1"); // Save to favorites and check that column groupbys were not lost expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["__count"], pivot_row_groupby: [], }; await toggleSaveFavorite(); await editFavoriteName("My favorite 1"); await saveFavorite(); // Set a column groupby await removeFacet(); // remove previously saved favorite await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); // Set a domain await toggleSearchBarMenu(); await toggleMenuItem("My Filter 2"); expectedContext = { group_by: [], pivot_column_groupby: ["customer", "product_id"], pivot_measures: ["__count"], pivot_row_groupby: [], }; await toggleSaveFavorite(); await editFavoriteName("My favorite 2"); await saveFavorite(); }); test("folded groups remain folded at reload", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); let values = ["29", "3", "32", "12", "12", "17", "3", "20"]; expect(getCurrentValues()).toBe(values.join(",")); // expand a col group await contains("thead .o_pivot_header_cell_closed:eq(1)").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); values = ["29", "2", "1", "32", "12", "12", "17", "2", "1", "20"]; expect(getCurrentValues()).toBe(values.join(",")); // expand a row group await contains("tbody .o_pivot_header_cell_closed:eq(1)").click(); await contains(".o-dropdown--menu .dropdown-item:eq(3)").click(); values = ["29", "2", "1", "32", "12", "12", "17", "2", "1", "20", "17", "2", "1", "20"]; expect(getCurrentValues()).toBe(values.join(",")); // reload (should keep folded groups folded as col/row groupbys didn't change) await toggleSearchBarMenu(); await toggleMenuItem("Dummy Filter"); expect(getCurrentValues()).toBe(values.join(",")); await contains(".o_pivot_expand_button").click(); // sanity check of what the table should look like if all groups are // expanded, to ensure that the former asserts are pertinent values = [ "12", "17", "2", "1", "32", "12", "12", "12", "12", "17", "2", "1", "20", "17", "2", "1", "20", ]; expect(getCurrentValues()).toBe(values.join(",")); }); test("Empty results keep groupbys", async () => { expect.assertions(6); const expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["__count"], pivot_row_groupby: [], }; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContext); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, }); // Set a column groupby await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); expect("table").toHaveCount(1); // Set a domain for empty results await toggleSearchBarMenu(); await toggleMenuItem("My Filter 1"); expect("table").toHaveCount(0); await toggleSaveFavorite(); await editFavoriteName("My favorite 1"); await saveFavorite(); // Set a domain for not empty results await removeFacet(); // remove previously saved favorite expect("table").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("My Filter 2"); expect("table").toHaveCount(1); await toggleSaveFavorite(); await editFavoriteName("My favorite 2"); await saveFavorite(); }); test("correctly uses pivot_ keys from the context", async () => { // in this test, we use "foo" as a measure Partner._fields.foo = fields.Integer({ groupable: false, }); Partner._fields.amount = fields.Float(); await mountView({ type: "pivot", resModel: "partner", arch: ` `, context: { pivot_measures: ["foo"], pivot_column_groupby: ["customer"], pivot_row_groupby: ["product_id"], }, }); expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(First)").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(Second)").toHaveCount(1); expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xphone)").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xpad)").toHaveCount(1); expect("tbody tr td:eq(2)").toHaveText("32"); }); test.tags("desktop"); test("clear table cells data after closeGroup", async () => { await mountView({ type: "pivot", resModel: "partner", arch: "", groupBy: ["product_id"], }); await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-toggle").hover(); await contains(".o-overlay-item:nth-child(2) .o-dropdown--menu .dropdown-item:eq(3)").click(); // close and reopen row groupings after changing value Partner._records.find((r) => r.product_id === 37).date = "2016-10-27"; await contains("tbody .o_pivot_header_cell_opened").click(); await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); expect(".o_pivot_cell_value:eq(4)").toHaveText(""); // xphone December 2016 // invert axis, and reopen column groupings await contains(".o_pivot_buttons .o_pivot_flip_button").click(); await contains("thead .o_pivot_header_cell_opened").click(); await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); expect(".o_pivot_cell_value:eq(3)").toHaveText(""); // December 2016 xphone }); test("correctly group data after flip (1)", async () => { Partner._views["pivot,false"] = ``; Partner._views[ "search,false" ] = ``; Partner._views["list,false"] = ``; Partner._views["form,false"] = ``; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], context: { group_by: ["product_id"] }, }); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "xpad"]); // flip axis await contains(".o_pivot_flip_button").click(); expect(queryAllTexts("tbody th")).toEqual(["Total"]); // select filter "Bayou" in control panel await toggleSearchBarMenu(); await toggleMenuItem("Bayou"); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "xpad"]); // close row header "Total" await contains("tbody .o_pivot_header_cell_opened").click(); expect(queryAllTexts("tbody th")).toEqual(["Total"]); }); test("correctly group data after flip (2)", async () => { Partner._views["pivot,false"] = ``; Partner._views[ "search,false" ] = ``; Partner._views["list,false"] = ``; Partner._views["form,false"] = `
`; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], context: { group_by: ["product_id"] }, }); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "xpad"]); // select filter "Bayou" in control panel await toggleSearchBarMenu(); await toggleMenuItem("Bayou"); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "xpad"]); // flip axis await contains(".o_pivot_flip_button").click(); expect(queryAllTexts("tbody th")).toEqual(["Total"]); // unselect filter "Bayou" in control panel await toggleSearchBarMenu(); await toggleMenuItem("Bayou"); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "xpad"]); // close row header "Total" await contains("tbody .o_pivot_header_cell_opened").click(); expect(queryAllTexts("tbody th")).toEqual(["Total"]); }); test("correctly uses pivot_ keys from the context (at reload)", async () => { // in this test, we use "foo" as a measure Partner._fields.foo = fields.Integer({ groupable: false, }); Partner._fields.amount = fields.Float(); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect("tbody tr td.o_pivot_cell_value:eq(4)").toHaveText("0.00"); await toggleSearchBarMenu(); await toggleMenuItem("My fake favorite"); expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(First)").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(Second)").toHaveCount(1); expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xphone)").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xpad)").toHaveCount(1); expect("tbody tr td:eq(2)").toHaveText("32"); }); test("correctly use group_by key from the context", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, groupBy: ["product_id"], }); expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(First)").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(Second)").toHaveCount(1); expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xphone)").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xpad)").toHaveCount(1); expect("tbody tr td:eq(2)").toHaveText("32"); }); test("correctly uses pivot_row_groupby key with default groupBy from the context", async () => { Partner._fields.amount = fields.Float(); await mountView({ type: "pivot", resModel: "partner", arch: ` `, groupBy: ["customer"], context: { pivot_row_groupby: ["product_id"], }, }); expect("thead .o_pivot_header_cell_opened").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(First)").toHaveCount(1); expect("thead .o_pivot_header_cell_closed:contains(Second)").toHaveCount(1); // With pivot_row_groupby, groupBy customer should replace and eventually display product_id expect("tbody .o_pivot_header_cell_opened").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xphone)").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed:contains(xpad)").toHaveCount(1); }); test("pivot still handles __count__ measure", async () => { expect.assertions(4); Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", aggregator: null, compute: () => 1, groupable: false, }); Partner._fields.foo = fields.Integer({ aggregator: null, groupable: false, }); // for retro-compatibility reasons, the pivot view still handles // '__count__' measure. onRpc("read_group", ({ kwargs }) => { expect(kwargs.fields).toEqual(["__count"]); }); await mountView({ type: "pivot", resModel: "partner", arch: "", context: { pivot_measures: ["__count__"], }, }); await contains(".o_pivot_buttons button.dropdown-toggle").click(); const dropdownMenu = getDropdownMenu(".o_pivot_buttons button.dropdown-toggle"); expect(queryAll(".dropdown-item", { root: dropdownMenu })).toHaveLength(1); expect(queryOne(".dropdown-item")).toHaveText("Count"); expect(queryOne(".dropdown-item")).toHaveClass("selected"); }); test("not use a many2one as a measure by default", async () => { expect.assertions(3); Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", compute: () => 1, aggregator: null, groupable: false, }); Partner._fields.foo = fields.Integer({ aggregator: null, groupable: false, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_buttons button.dropdown-toggle").click(); const dropdownMenu = getDropdownMenu(".o_pivot_buttons button.dropdown-toggle"); expect(queryAll(".dropdown-item", { root: dropdownMenu })).toHaveLength(1); expect(queryText(".dropdown-item", { root: dropdownMenu })).toBe("Count"); expect(queryOne(".dropdown-item", { root: dropdownMenu })).toHaveClass("selected"); }); test("pivot view with many2one field as a measure", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("table tbody tr")).toEqual(["Total \n1\n \n1\n \n2\n \n2"]); }); test("pivot view with reference field as a measure", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("table tbody tr")).toEqual(["Total \n1\n \n1\n \n2\n \n4"]); }); test.tags("desktop"); test("m2o as measure, drilling down into data", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains("tbody .o_pivot_header_cell_closed").click(); // click on date by month const dropdownMenu = getDropdownMenu("tbody .o_pivot_header_cell_closed"); await contains(queryFirst(".dropdown-toggle", { root: dropdownMenu })).hover(); await contains(queryOne(".o-dropdown-item:contains(Month)")).click(); expect(queryAllTexts(".o_pivot_cell_value")).toEqual(["2", "1", "1", "2"]); }); test("Row and column groupbys plus a domain", async () => { expect.assertions(3); const expectedContext = { group_by: [], pivot_column_groupby: ["customer"], pivot_measures: ["foo"], pivot_row_groupby: ["product_id"], }; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContext); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // Set a column groupby await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); // Set a Row groupby await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); // Add a filter await toggleSearchBarMenu(); await toggleMenuItem("Some Filter"); expect("tbody .o_pivot_header_cell_closed").toHaveCount(1); expect("tbody .o_pivot_header_cell_closed").toHaveText("xpad"); // Save current search to favorite await toggleSaveFavorite(); await editFavoriteName("My favorite"); await saveFavorite(); }); test("parallel data loading should discard all but the last one", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(".o_pivot_cell_value").toHaveCount(1); expect("tbody tr").toHaveCount(1); def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("Product"); await toggleMenuItem("Customer"); expect(".o_pivot_cell_value").toHaveCount(1); expect("tbody tr").toHaveCount(1); def.resolve(); await animationFrame(); expect(".o_pivot_cell_value").toHaveCount(6); expect("tbody tr").toHaveCount(6); }); test("pivot measures should be alphabetically sorted", async () => { Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", aggregator: null, compute: () => 1, groupable: false, }); // It's important to compare capitalized and lowercased words // to be sure the sorting is effective with both of them Partner._fields.bouh = fields.Integer({ string: "bouh" }); Partner._fields.modd = fields.Integer({ string: "modd" }); Partner._fields.zip = fields.Integer({ string: "Zip" }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_pivot_buttons button.dropdown-toggle").click(); expect(queryAllTexts(".o-dropdown--menu .dropdown-item")).toEqual([ "bouh", "Foo", "modd", "Zip", "Count", ]); }); test("pivot view should use default order for auto sorting", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("thead th.o_pivot_measure_row").toHaveClass("o_pivot_sort_order_asc"); }); test("pivot view can be flipped", async () => { var rpcCount = 0; onRpc(() => { rpcCount++; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect("tbody tr").toHaveCount(3); let values = ["4", "1", "3"]; expect(getCurrentValues()).toBe(values.join()); rpcCount = 0; await contains(".o_pivot_flip_button").click(); expect(rpcCount).toBe(0); expect("tbody tr").toHaveCount(1, { message: "should have 1 rows: 1 for the main header", }); values = ["1", "3", "4"]; expect(getCurrentValues()).toBe(values.join()); }); test("rendering of pivot view with comparison", async () => { Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", aggregator: null, compute: () => 1, groupable: false, }); Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; mockDate("2016-12-20T1:00:00"); onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual({ pivot_measures: ["__count"], pivot_column_groupby: [], pivot_row_groupby: ["product_id"], group_by: [], comparison: { comparisonId: "previous_period", comparisonRange: ["&", ["date", ">=", "2016-11-01"], ["date", "<=", "2016-11-30"]], comparisonRangeDescription: "November 2016", fieldDescription: "Date", fieldName: "date", range: ["&", ["date", ">=", "2016-12-01"], ["date", "<=", "2016-12-31"]], rangeDescription: "December 2016", }, }); return true; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: 1 }, }); // with no data await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); expect("p.o_view_nocontent_empty_folder").toHaveCount(1); await toggleMenuItem("Date"); await toggleMenuItemOption("Date", "December"); await toggleMenuItemOption("Date", "2016"); await toggleMenuItemOption("Date", "2015"); expect(".o_pivot thead tr:last th").toHaveCount(9); let values = ["19", "0", "-100%", "0", "13", "100%", "19", "13", "-31.58%"]; expect(getCurrentValues()).toBe(values.join()); // with data, with row groupby await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(4)").click(); values = [ "19", "0", "-100%", "0", "13", "100%", "19", "13", "-31.58%", "19", "0", "-100%", "0", "1", "100%", "19", "1", "-94.74%", "0", "12", "100%", "0", "12", "100%", ]; expect(getCurrentValues()).toBe(values.join()); await contains(".o_pivot_buttons button.dropdown-toggle").click(); await contains(".o-dropdown--menu .dropdown-item:eq(0)").click(); await contains(".o-dropdown--menu .dropdown-item:eq(1)").click(); values = ["2,0,-100%,0,2,100%,2,2,0%,2,0,-100%,0,1,100%,2,1,-50%,0,1,100%,0,1,100%"]; expect(getCurrentValues()).toBe(values.join()); await contains("thead .o_pivot_header_cell_opened").click(); values = ["2", "2", "0%", "2", "1", "-50%", "0", "1", "100%"]; expect(getCurrentValues()).toBe(values.join()); await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Fav"); await saveFavorite(); }); test("export data in excel with comparison", async () => { expect.assertions(5); Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; mockDate("2016-12-20T1:00:00"); patchWithCleanup(download, { _download: ({ url, data }) => { data = JSON.parse(data.data); for (const l of data.col_group_headers) { const titles = l.map((o) => o.title); expect.step(titles); } const measures = data.measure_headers.map((o) => o.title); expect.step(measures); const origins = data.origin_headers.map((o) => o.title); expect.step(origins); expect.step(data.measure_count); expect.step(data.origin_count); const valuesLength = data.rows.map((o) => o.values.length); expect.step(valuesLength); expect(url).toBe("/web/pivot/export_xlsx"); return Promise.resolve(); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: 1 }, }); // open comparison menu await toggleSearchBarMenu(); // compare October 2016 to September 2016 await toggleMenuItem("Date: Previous period"); // With the data above, the time ranges contain no record. expect("p.o_view_nocontent_empty_folder").toHaveCount(1); // export data should be impossible since the pivot buttons // are deactivated (exception: the 'Measures' button). expect(".o_pivot_buttons button.o_pivot_download").not.toBeEnabled(); await toggleMenuItem("Date"); await toggleMenuItemOption("Date", "December"); await toggleMenuItemOption("Date", "October"); expect(".o_pivot_buttons button.o_pivot_download").toBeEnabled(); // With the data above, the time ranges contain some records. // export data. Should execute 'get_file' await contains(".o_pivot_buttons button.o_pivot_download").click(); expect.verifySteps([ // col group headers ["Total", ""], ["November 2016", "December 2016"], // measure headers ["Foo", "Foo", "Foo"], // origin headers [ "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", ], // number of 'measures' 1, // number of 'origins' 2, // rows values length [9], ]); }); test("rendering pivot view with comparison and count measure", async () => { let mockMock = false; let nbReadGroup = 0; Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-12-22"; Partner._records[3].date = "2016-12-03"; mockDate("2016-12-20T1:00:00"); onRpc("read_group", () => { if (mockMock) { nbReadGroup++; if (nbReadGroup === 4) { // this modification is necessary because mockReadGroup does not // properly reflect the server response when there is no record // and a groupby list of length at least one. return [{}]; } } }); await mountView({ type: "pivot", resModel: "partner", arch: '', searchViewArch: ` `, context: { search_default_date_filter: 1 }, }); mockMock = true; // compare December 2016 to November 2016 await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); const values = ["0", "4", "100%", "0", "2", "100%", "0", "2", "100%"]; expect(getCurrentValues()).toBe(values.join(",")); expect(".o_pivot_header_cell_closed").toHaveCount(3); }); test("can sort a pivot view with comparison by clicking on header", async () => { Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; mockDate("2016-12-20T1:00:00"); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: 1 }, }); // compare December 2016 to November 2016 await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); // initial sanity check let values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "2", "0", "-100%", "2", "0", "-100%", "17", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", ]; expect(getCurrentValues()).toBe(values.join()); // click on 'Foo' in column Total/Company (should sort by the period of interest, ASC) await contains(".o_pivot_measure_row").click(); values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "2", "0", "-100%", "2", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", "17", "0", "-100%", "17", "0", "-100%", ]; expect(getCurrentValues()).toBe(values.join()); // click again on 'Foo' in column Total/Company (should sort by the period of interest, DESC) await contains(".o_pivot_measure_row").click(); values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "17", "0", "-100%", "17", "0", "-100%", "2", "0", "-100%", "2", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", ]; expect(getCurrentValues()).toBe(values.join()); // click on 'This Month' in column Total/Individual/Foo await contains(".o_pivot_origin_row:eq(3)").click(); values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "17", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", "2", "0", "-100%", "2", "0", "-100%", ]; expect(getCurrentValues()).toBe(values.join()); // click on 'Previous Period' in column Total/Individual/Foo await contains(".o_pivot_origin_row:eq(4)").click(); values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "2", "0", "-100%", "2", "0", "-100%", "17", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", ]; expect(getCurrentValues()).toBe(values.join()); // click on 'Variation' in column Total/Foo await contains(".o_pivot_origin_row:eq(8)").click(); values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "2", "0", "-100%", "2", "0", "-100%", "17", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", ]; expect(getCurrentValues()).toBe(values.join()); }); test("Click on the measure list but not on a menu item", async () => { await mountView({ type: "pivot", resModel: "partner", // have at least a measure to have a separator in the Measures dropdown: // // Foo // ----- // Count arch: ``, }); expect(".o-dropdown--menu").toHaveCount(0); // open the "Measures" menu await contains(".o_pivot_buttons .dropdown-toggle").click(); expect(".o-dropdown--menu").toHaveCount(1); // click on the divider in the "Measures" menu does not crash await contains(".o-dropdown--menu .dropdown-divider").click(); // the menu should still be open expect(".o-dropdown--menu").toHaveCount(1); // click on the measure list but not on a menu item or the separator await contains(".o-dropdown--menu").click(); // the menu should still be open expect(".o-dropdown--menu").toHaveCount(1); }); test.tags("desktop"); test("Navigation list view for a group and back with breadcrumbs", async () => { expect.assertions(9); Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ` `; Partner._views["list,false"] = ``; Partner._views["form,false"] = `
`; let readGroupCount = 0; onRpc("read_group", ({ kwargs }) => { expect.step("read_group"); const domain = kwargs.domain; if ([0, 1].indexOf(readGroupCount) !== -1) { expect(domain).toEqual([]); } else if ([2, 3, 4, 5].indexOf(readGroupCount) !== -1) { expect(domain).toEqual([["foo", "=", 12]]); } readGroupCount++; }); onRpc("web_search_read", ({ kwargs }) => { expect.step("web_search_read"); const domain = kwargs.domain; expect(domain).toEqual(["&", ["customer", "=", 1], ["foo", "=", 12]]); }); await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [[false, "pivot"]], }); await toggleSearchBarMenu(); await toggleMenuItem("Bayou"); await animationFrame(); await contains(".o_pivot_cell_value:eq(1)").click(); expect(".o_list_view").toHaveCount(1); await contains(".o_control_panel ol.breadcrumb li.breadcrumb-item").click(); expect.verifySteps([ "read_group", "read_group", "read_group", "read_group", "web_search_read", "read_group", "read_group", ]); }); test("Cell values are kept when flippin a pivot view in comparison mode", async () => { Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; mockDate("2016-12-20T1:00:00"); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_date_filter: 1 }, }); // compare December 2016 to November 2016 await toggleSearchBarMenu(); await toggleMenuItem("Date: Previous period"); // initial sanity check let values = [ "17", "12", "-29.41%", "2", "1", "-50%", "19", "13", "-31.58%", "2", "0", "-100%", "2", "0", "-100%", "17", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "12", "100%", "0", "1", "100%", "0", "1", "100%", ]; expect(getCurrentValues()).toBe(values.join()); // flip table await contains(".o_pivot_flip_button").click(); values = [ "2", "0", "-100%", "17", "0", "-100%", "0", "12", "100%", "0", "1", "100%", "19", "13", "-31.58%", "17", "0", "-100%", "0", "12", "100%", "17", "12", "-29.41%", "2", "0", "-100%", "0", "1", "100%", "2", "1", "-50%", ]; expect(getCurrentValues()).toBe(values.join()); }); test("Flip then compare, table col groupbys are kept", async () => { Partner._records[0].date = "2016-12-15"; Partner._records[1].date = "2016-12-17"; Partner._records[2].date = "2016-11-22"; Partner._records[3].date = "2016-11-03"; mockDate("2016-12-20T1:00:00"); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(queryAllTexts("thead th")).toEqual([ "", "Total", "", "Company", "individual", "Foo", "Foo", "Foo", ]); expect(queryAllTexts("tbody th")).toEqual([ "Total", "2016-11-03", "2016-11-22", "2016-12-15", "2016-12-17", ]); // flip await contains(".o_pivot_flip_button").click(); expect(queryAllTexts("thead th")).toEqual([ "", "Total", "", "2016-11-03", "2016-11-22", "2016-12-15", "2016-12-17", "Foo", "Foo", "Foo", "Foo", "Foo", ]); expect(queryAllTexts("tbody th")).toEqual(["Total", "Company", "individual"]); // Filter on December 2016 await toggleSearchBarMenu(); await toggleMenuItem("Date"); await toggleMenuItemOption("Date", "December"); // compare December 2016 to November 2016 await toggleMenuItem("Date: Previous period"); expect(queryAllTexts("thead th")).toEqual([ "", "Total", "", "2016-11-03", "2016-11-22", "2016-12-15", "2016-12-17", "Foo", "Foo", "Foo", "Foo", "Foo", "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", "November 2016", "December 2016", "Variation", ]); expect(queryAllTexts("tbody th")).toEqual(["Total", "Company", "individual"]); }); test("correctly compute group domain when a date field has false value", async () => { expect.assertions(1); Partner._records.forEach((r) => (r.date = false)); mockDate("2016-12-20T1:00:00"); mockService("action", { doAction(action) { expect(action.domain).toEqual([["date", "=", false]]); return Promise.resolve(true); }, }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); await contains(".o_value:eq(1)").click(); }); test("Does not identify 'false' with false as keys when creating group trees", async () => { Partner._fields.favorite_animal = fields.Char(); Partner._records[0].favorite_animal = "Dog"; Partner._records[1].favorite_animal = "false"; Partner._records[2].favorite_animal = "None"; mockDate("2016-12-20T1:00:00"); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Count"]); expect(queryAllTexts("tbody th")).toEqual(["Total", "Dog", "None", "false", "None"]); }); test("group bys added via control panel and expand Header do not stack", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Foo"]); expect(queryAllTexts("tbody th")).toEqual(["Total"]); // open group by menu and add new groupby await toggleSearchBarMenu(); await contains(`.o_add_custom_group_menu`).select("company_type"); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Foo"]); expect(queryAllTexts("tbody th")).toEqual(["Total", "Company", "individual"]); // Set a Row groupby await contains("tbody tr:nth-child(2) .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .o_menu_item:nth-child(5)").click(); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Foo"]); expect(queryAllTexts("tbody th")).toEqual(["Total", "Company", "xphone", "xpad", "individual"]); // open groupby menu generator and add a new groupby await toggleSearchBarMenu(); await contains(`.o_add_custom_group_menu`).select("bar"); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Foo"]); expect(queryAllTexts("tbody th")).toEqual([ "Total", "Company", "Yes", "individual", "No", "Yes", ]); }); test("display only one dropdown menu", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); // add a col groupby on Product await contains("thead th.o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item:eq(5)").click(); // Click on the two header dropdown togglers await contains("thead th.o_pivot_header_cell_closed:eq(0)").click(); await contains("thead th.o_pivot_header_cell_closed:eq(1)").click(); expect(".o-dropdown--menu").toHaveCount(1); }); test("Server order is kept by default", async () => { let isSecondReadGroup = false; onRpc("read_group", () => { if (isSecondReadGroup) { return [ { customer: [2, "Second"], foo: 18, __count: 2, __domain: [["customer", "=", 2]], }, { customer: [1, "First"], foo: 14, __count: 2, __domain: [["customer", "=", 1]], }, ]; } isSecondReadGroup = true; }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); const values = [ "32", // Total Value "18", // Second "14", // First ]; expect(getCurrentValues()).toBe(values.join()); }); test("pivot rendering with boolean field", async () => { Partner._fields.bar = fields.Boolean({ aggregator: "bool_or", }); Partner._records = [ { id: 1, bar: true, date: "2019-12-14" }, { id: 2, bar: false, date: "2019-05-14" }, ]; await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect('tbody tr:contains("2019-12-14")').toHaveCount(1); expect('tbody tr:contains("2019-12-14") [type="checkbox"]').toBeChecked(); expect('tbody tr:contains("2019-05-14")').toHaveCount(1); expect('tbody tr:contains("2019-05-14") [type="checkbox"]').not.toBeChecked(); }); test.tags("desktop"); test("empty pivot view with action helper", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ` `; await mountView({ type: "pivot", resModel: "partner", context: { search_default_small_than_0: true }, noContentHelp: markup(`

click to add a foo

`), config: { views: [[false, "search"]], }, }); expect(".o_view_nocontent .abc").toHaveCount(1); expect("table").toHaveCount(0); await removeFacet(); expect(".o_view_nocontent .abc").toHaveCount(0); expect("table").toHaveCount(1); }); test.tags("desktop"); test("empty pivot view with sample data", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ` `; await mountView({ type: "pivot", resModel: "partner", context: { search_default_small_than_0: true }, noContentHelp: markup('

click to add a foo

'), config: { views: [[false, "search"]], }, }); expect(".o_pivot_view .o_content").toHaveClass("o_view_sample_data"); expect(".o_view_nocontent .abc").toHaveCount(1); await removeFacet(); expect(".o_pivot_view .o_content").not.toHaveClass("o_view_sample_data"); expect(".o_view_nocontent .abc").toHaveCount(0); expect("table").toHaveCount(1); }); test("non empty pivot view with sample data", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ` `; await mountView({ type: "pivot", resModel: "partner", noContentHelp: markup('

click to add a foo

'), config: { views: [[false, "search"]], }, }); expect("document").not.toHaveClass("o_view_sample_data"); expect(".o_view_nocontent .abc").toHaveCount(0); expect("table").toHaveCount(1); await toggleSearchBarMenu(); await toggleMenuItem("Small Than 0"); expect("document").not.toHaveClass("o_view_sample_data"); expect(".o_view_nocontent .abc").toHaveCount(1); expect("table").toHaveCount(0); }); test.tags("desktop"); test("pivot is reloaded when leaving and coming back", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ``; Partner._views["list,false"] = ``; onRpc("partner", "*", ({ method }) => { expect.step(method); }); onRpc("/web/webclient/load_menus/*", () => { expect.step("/web/webclient/load_menus"); }); await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [ [false, "pivot"], [false, "list"], ], }); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["4", "2", "2"].join(",")); expect.verifySteps(["/web/webclient/load_menus", "get_views", "read_group", "read_group"]); // switch to list view await contains(".o_control_panel .o_switch_view.o_list").click(); expect(".o_list_view").toHaveCount(1); expect.verifySteps(["web_search_read"]); // switch back to pivot await contains(".o_control_panel .o_switch_view.o_pivot").click(); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["4", "2", "2"].join(",")); expect.verifySteps(["read_group", "read_group"]); }); test.tags("desktop"); test("expanded groups are kept when leaving and coming back", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ``; Partner._views["list,false"] = ``; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [ [false, "pivot"], [false, "list"], ], }); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["4", "2", "2"].join(",")); // drill down first row group (group by company_type) await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item").click(); expect(getCurrentValues()).toBe(["4", "2", "1", "1", "2"].join(",")); // switch to list view await contains(".o_control_panel .o_switch_view.o_list").click(); expect(".o_list_view").toHaveCount(1); // switch back to pivot await contains(".o_control_panel .o_switch_view.o_pivot").click(); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["4", "2", "1", "1", "2"].join(",")); }); test.tags("desktop"); test("sorted rows are kept when leaving and coming back", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ``; Partner._views["list,false"] = ``; await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [ [false, "pivot"], [false, "list"], ], }); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // sort the first group await contains("th.o_pivot_measure_row").click(); await contains("th.o_pivot_measure_row").click(); expect(getCurrentValues()).toBe(["32", "20", "12"].join(",")); // switch to list view await contains(".o_control_panel .o_switch_view.o_list").click(); expect(".o_list_view").toHaveCount(1); // switch back to pivot await contains(".o_control_panel .o_switch_view.o_pivot").click(); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["32", "20", "12"].join(",")); }); test.tags("desktop"); test("correctly handle concurrent reloads", async () => { Partner._views["pivot,false"] = ` `; Partner._views["search,false"] = ``; Partner._views["list,false"] = ``; let def; let readGroupCount = 0; onRpc("read_group", () => { if (def) { readGroupCount++; if (readGroupCount === 2) { // slow down last read_group of first reload return def; } } }); await mountWithCleanup(WebClient); await getService("action").doAction({ res_model: "partner", type: "ir.actions.act_window", views: [ [false, "pivot"], [false, "list"], ], }); expect(".o_pivot_view").toHaveCount(1); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // drill down first row group (group by company_type) await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item").click(); expect(getCurrentValues()).toBe(["32", "12", "12", "20"].join(",")); // reload twice by clicking on pivot view switcher def = new Deferred(); await contains(".o_control_panel .o_switch_view.o_pivot").click(); await contains(".o_control_panel .o_switch_view.o_pivot").click(); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["32", "12", "12", "20"].join(",")); }); test("consecutively toggle several measures", async () => { let def; Partner._fields.foo2 = fields.Integer({ groupable: false, }); onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Toggle several measures (the reload is blocked, so all measures should be toggled in once) def = new Deferred(); await toggleMenu("Measures"); await toggleMenuItem("Foo2"); // add foo2 expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); await toggleMenuItem("Foo"); // remove foo expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); await toggleMenuItem("Count"); // add count expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["0", "4", "0", "1", "0", "3"].join(",")); }); test("flip axis while loading a filter", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); const values = ["2", "1", "29", "32", "12", "12", "2", "1", "17", "20"]; expect(getCurrentValues()).toBe(values.join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(values.join(",")); // Flip axis await contains(".o_pivot_flip_button").click(); expect(getCurrentValues()).toBe(values.join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["20", "2", "1", "17"].join(",")); }); test("sort rows while loading a filter", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Sort rows (this operation should be ignored as it concerns the old // table, which will be replaced soon) await contains("th.o_pivot_measure_row").click(); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["20", "20"].join(",")); }); test("close a group while loading a filter", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Close a group (this operation should be ignored as it concerns the old // table, which will be replaced soon) await contains("tbody .o_pivot_header_cell_opened").click(); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["20", "20"].join(",")); }); test("add a groupby while loading a filter", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Add a groupby (this operation should be ignored as it concerns the old // table, which will be replaced soon) await contains("thead .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item").click(); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["20", "20"].join(",")); }); test("expand a group while loading a filter", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); // Add a groupby, to have a group to expand afterwards await contains("tbody .o_pivot_header_cell_closed").click(); await contains(".o-dropdown--menu .dropdown-item").click(); expect(getCurrentValues()).toBe(["32", "12", "12", "20"].join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(["32", "12", "12", "20"].join(",")); // Expand a group (this operation should be ignored as it concerns the old // table, which will be replaced soon) await contains("tbody .o_pivot_header_cell_closed:eq(1)").click(); expect(getCurrentValues()).toBe(["32", "12", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["20", "20"].join(",")); }); test("concurrent reloads: add a filter, and directly toggle a measure", async () => { let def; onRpc("read_group", () => def); await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, }); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Set a domain (this reload is delayed) def = new Deferred(); await toggleSearchBarMenu(); await toggleMenuItem("My Filter"); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); // Toggle a measure await toggleMenu("Measures"); await toggleMenuItem("Count"); expect(getCurrentValues()).toBe(["32", "12", "20"].join(",")); def.resolve(); await animationFrame(); expect(getCurrentValues()).toBe(["12", "1", "12", "1"].join(",")); }); test("if no measure is set in arch, 'Count' is used as measure initially", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ``, }); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Count"]); }); test("if (at least) one measure is set in arch and display_quantity is false or unset, 'Count' is not used as measure initially", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Foo"]); }); test("if (at least) one measure is set in arch and display_quantity is true, 'Count' is used as measure initially", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Count", "Foo"]); }); test("'Measures' menu when there is no measurable fields", async () => { delete Partner._fields.foo; delete Partner._fields.computed_field; Partner._records = [{ id: 1, display_name: "The one" }]; await mountView({ type: "pivot", resModel: "partner", arch: ``, }); await toggleMenu("Measures"); // "Count" is the only measure available expect(queryAllTexts(".o-dropdown--menu .o_menu_item")).toEqual(["Count"]); // No separator should be displayed in the menu "Measures" expect(".o-dropdown--menu div.dropdown-divider").toHaveCount(0); }); test("pivot_row_groupby should be also used after first load", async () => { const ids = [1, 2]; const expectedContexts = [ { group_by: ["bar"], pivot_column_groupby: [], pivot_measures: ["__count"], pivot_row_groupby: ["product_id"], }, { group_by: ["bar", "customer"], pivot_column_groupby: [], pivot_measures: ["__count"], pivot_row_groupby: ["customer"], }, ]; onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual(expectedContexts.shift()); return ids.shift(); }); await mountView({ type: "pivot", resModel: "partner", arch: ``, searchViewArch: ` `, groupBy: ["bar"], }); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "No", "Yes"]); await contains("tbody th").click(); // click on row header "Total" await contains("tbody th").click(); // click on row header "Total" await contains(".o-dropdown--menu .o_menu_item").click(); // select "Product" expect(queryAllTexts("th").slice(3)).toEqual(["Total", "xphone", "xpad"]); await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Favorite"); await saveFavorite(); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "xphone", "xpad"]); await removeFacet(); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "No", "Yes"]); await toggleSearchBarMenu(); await toggleMenuItem("Favorite"); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "xphone", "xpad"]); await toggleMenuItem("customer"); expect(queryAllTexts("th").slice(3)).toEqual([ "Total", "xphone", "First", "xpad", "First", "Second", ]); await contains("tbody th").click(); // click on row header "Total" await contains("tbody th").click(); // click on row header "Total" await contains(".o-dropdown--menu .o_menu_item:eq(1)").click(); // select "Customer" expect(queryAllTexts("th").slice(3)).toEqual(["Total", "First", "Second"]); await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Favorite 2"); await saveFavorite(); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "First", "Second"]); }); test("pivot_row_groupby should be also used after first load (2)", async () => { await mountView({ type: "pivot", resModel: "partner", groupBy: ["product_id"], arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "Favorite", id: 1, context: ` { "group_by": [], "pivot_row_groupby": ["customer"], "pivot_col_groupby": [], "pivot_measures": ["foo"], } `, sort: "[]", domain: "", is_default: false, model_id: "foo", action_id: false, }, ], }); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "xphone", "xpad"]); await toggleSearchBarMenu(); await toggleMenuItem("Favorite"); expect(queryAllTexts("th").slice(3)).toEqual(["Total", "First", "Second"]); }); test("specific pivot keys in action context must have less importance than in favorite context", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ``, context: { pivot_column_groupby: [], pivot_measures: ["__count"], pivot_row_groupby: [], }, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "My favorite", id: 1, context: `{ "pivot_column_groupby": ["bar"], "pivot_measures": ["computed_field"], "pivot_row_groupby": [], }`, sort: "[]", domain: "", is_default: true, model_id: "partner", action_id: false, }, { user_id: [2, "Mitchell Admin"], name: "My favorite 2", id: 2, context: `{ "pivot_column_groupby": ["product_id"], "pivot_measures": ["computed_field", "__count"], "pivot_row_groupby": [], }`, sort: "[]", domain: "", is_default: false, model_id: "partner", action_id: false, }, ], }); expect(queryAllTexts("th").slice(1, 6)).toEqual([ "Total", "", "No", "Yes", "Computed and not stored", ]); await toggleSearchBarMenu(); await toggleMenuItem("My favorite 2"); expect(queryAllTexts("th").slice(1, 11)).toEqual([ "Total", "", "xphone", "xpad", "Computed and not stored", "Count", "Computed and not stored", "Count", "Computed and not stored", "Count", ]); }); test("favorite pivot_measures should be used even if found also in global context", async () => { // Computed and not stored displayed in "Measures" menu Partner._fields.computed_field = fields.Integer({ string: "Computed and not stored", compute: () => 1, store: true, groupable: false, }); onRpc("create_or_replace", ({ args }) => { expect(args[0].context).toEqual({ group_by: [], pivot_column_groupby: [], pivot_measures: ["computed_field"], pivot_row_groupby: [], }); return 1; }); await mountView({ type: "pivot", resModel: "partner", arch: ``, context: { pivot_measures: ["__count"], }, }); expect(queryAllTexts("th").slice(1, 3)).toEqual(["Total", "Count"]); await toggleMenu("Measures"); await toggleMenuItem("Count"); await toggleMenuItem("Computed and not stored"); expect(getFacetTexts()).toEqual([]); expect(queryAllTexts("th").slice(1, 3)).toEqual(["Total", "Computed and not stored"]); await toggleSearchBarMenu(); await toggleSaveFavorite(); await editFavoriteName("Favorite"); await saveFavorite(); expect(getFacetTexts()).toEqual(["Favorite"]); expect(queryAllTexts("th").slice(1, 3)).toEqual(["Total", "Computed and not stored"]); }); test.tags("desktop"); test("filter -> sort -> unfilter should not crash", async () => { await mountView({ type: "pivot", resModel: "partner", arch: ` `, searchViewArch: ` `, context: { search_default_xphone: true, }, }); expect(getFacetTexts()).toEqual(["xphone"]); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "Yes"]); expect(getCurrentValues()).toBe(["1", "1", "1"].join()); await contains(".o_pivot_measure_row").click(); await toggleSearchBarMenu(); await toggleMenuItem("xphone"); expect(getFacetTexts()).toEqual([]); expect(queryAllTexts("tbody th")).toEqual(["Total", "xphone", "Yes", "xpad"]); expect(getCurrentValues()).toBe(["4", "1", "1", "3"].join()); }); test("no class 'o_view_sample_data' when real data are presented", async () => { Partner._records = []; await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect(".o_pivot_view .o_view_sample_data").toHaveCount(1); expect(".o_pivot_view table").toHaveCount(1); await toggleMenu("Measures"); await toggleMenuItem("Foo"); expect(".o_pivot_view .o_view_sample_data").toHaveCount(0); expect(".o_pivot_view table").toHaveCount(0); }); test("group by properties in pivot view", async () => { onRpc("/web/dataset/call_kw/partner/web_search_read", async (request) => { const { params } = await request.json(); if (params.kwargs.specification?.properties_definition) { expect.step("fetch_definition"); } }); onRpc("/web/dataset/call_kw/partner/read_group", async (request) => { const { params } = await request.json(); if (params.kwargs.groupby?.includes("properties.my_char")) { expect.step("read_group"); return [ { "properties.my_char": false, __domain: [["properties.my_char", "=", false]], __count: 2, }, { "properties.my_char": "aaa", __domain: [["properties.my_char", "=", "aaa"]], __count: 1, }, { "properties.my_char": "bbb", __domain: [["properties.my_char", "=", "bbb"]], __count: 1, }, ]; } }); await mountView({ type: "pivot", resModel: "partner", arch: "", searchViewArch: ` `, }); expect(".o_value").toHaveCount(1); expect(".o_value").toHaveText("4"); await contains(".border-top-0 span").click(); expect.verifySteps([]); expect(".o_accordion_toggle").toHaveText("Properties"); await contains(".o_accordion_toggle:contains(Properties)").click(); await animationFrame(); expect.verifySteps(["fetch_definition"]); await contains(".o_accordion_values .o_menu_item").click(); await animationFrame(); expect.verifySteps(["read_group"]); const cells = queryAll(".o_value"); expect(cells).toHaveLength(4); expect(cells[0]).toHaveText("2"); expect(cells[1]).toHaveText("1"); expect(cells[2]).toHaveText("1"); expect(cells[3]).toHaveText("4"); const columns = queryAll(".o_pivot_header_cell_closed"); expect(columns).toHaveLength(4); expect(columns[0]).toHaveText("None"); expect(columns[1]).toHaveText("aaa"); expect(columns[2]).toHaveText("bbb"); }); test("avoid duplicates in read_group parameter 'groupby'", async () => { onRpc("read_group", ({ kwargs }) => { expect.step(kwargs.groupby); }); await mountView({ type: "pivot", resModel: "partner", arch: ` `, }); expect.verifySteps([[], ["date:month"]]); }); test("Close header dropdown when a simple groupby is selected", async function (assert) { await mountView({ type: "pivot", resModel: "partner", arch: ``, }); expect(".o-overlay-container .dropdown-menu").toHaveCount(0); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Count"]); await contains("thead .o_pivot_header_cell_closed").click(); expect(".o-overlay-container .dropdown-menu").toHaveCount(1); await contains(".o-overlay-container .o-dropdown--menu .dropdown-item").click(); expect(".o-overlay-container .dropdown-menu").toHaveCount(0); expect(queryAllTexts("thead th")).toEqual([ "", "Total", "", "Company", "individual", "Count", "Count", "Count", ]); }); test.tags("desktop"); test("Close header dropdown when a simple date groupby option is selected", async function (assert) { await mountView({ type: "pivot", resModel: "partner", arch: ``, }); expect(".o-overlay-container .dropdown-menu").toHaveCount(0); expect(queryAllTexts("thead th")).toEqual(["", "Total", "Count"]); await contains("thead .o_pivot_header_cell_closed").click(); expect(".o-overlay-container .dropdown-menu").toHaveCount(1); // open the Date sub dropdown await contains(".o-dropdown--menu .dropdown-toggle.o_menu_item").hover(); const subDropdownMenu = getDropdownMenu(".o-dropdown--menu .dropdown-toggle.o_menu_item"); await contains(queryOne(".dropdown-item:eq(2)", { root: subDropdownMenu })).click(); expect(".o-overlay-container .dropdown-menu").toHaveCount(0); expect(queryAllTexts("thead th")).toEqual([ "", "Total", "", "April 2016", "October 2016", "December 2016", "Count", "Count", "Count", "Count", ]); }); test("missing property field definition is fetched", async function () { onRpc(({ method, kwargs }) => { if (method === "read_group" && kwargs.groupby?.includes("properties.my_char")) { expect.step(JSON.stringify(kwargs.groupby)); return [ { "properties.my_char": false, __domain: [["properties.my_char", "=", false]], __count: 2, }, { "properties.my_char": "aaa", __domain: [["properties.my_char", "=", "aaa"]], __count: 1, }, ]; } else if (method === "get_property_definition") { return { name: "my_char", type: "char", }; } }); await mountView({ type: "pivot", resModel: "partner", arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "My Filter", id: 5, context: `{"group_by": ['properties.my_char']}`, sort: "[]", domain: "[]", is_default: true, model_id: "partner", action_id: false, }, ], }); expect.verifySteps([`["properties.my_char"]`]); expect(getCurrentValues()).toBe("4,2,1"); }); test("missing deleted property field definition is created", async function (assert) { onRpc(({ method, kwargs }) => { if (method === "read_group" && kwargs.groupby?.includes("properties.my_char")) { expect.step(JSON.stringify(kwargs.groupby)); return [ { "properties.my_char": false, __domain: [["properties.my_char", "=", false]], __count: 2, }, { "properties.my_char": "aaa", __domain: [["properties.my_char", "=", "aaa"]], __count: 1, }, ]; } else if (method === "get_property_definition") { return {}; } }); await mountView({ type: "pivot", resModel: "partner", arch: ``, irFilters: [ { user_id: [2, "Mitchell Admin"], name: "My Filter", id: 5, context: `{"group_by": ['properties.my_char']}`, sort: "[]", domain: "[]", is_default: true, model_id: "partner", action_id: false, }, ], }); expect.verifySteps([`["properties.my_char"]`]); expect(getCurrentValues()).toBe("4,2,1"); });