4561 lines
134 KiB
JavaScript
4561 lines
134 KiB
JavaScript
/** @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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `<pivot class="foobar-class"/>`,
|
||
});
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="bouh" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure" widget="float_time"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" string="BAR" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="foo" type="row"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="foo" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="fubar" string="fubar" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="foo2" type="measure" invisible="1"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="date" type="row"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="computed_field" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="price_nonaggregable"/>
|
||
</pivot>`,
|
||
});
|
||
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"] = `<form/>`;
|
||
Partner._views["list,false"] = `<list/>`;
|
||
Partner._views["kanban,5"] = `<kanban/>`;
|
||
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
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: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="col"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="date" type="col"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="date" type="col"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot disable_linking="True">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `<pivot><field name="product_id" type="row"/></pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
});
|
||
|
||
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: "<pivot></pivot>",
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: "<pivot></pivot>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '>', 10)]"/>
|
||
</search>`,
|
||
});
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="bar" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
// TOASK DAM: <search><field/></search> won´t appear in groupbymenu ?
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="bar" string="bar" context="{'group_by': 'bar'}"/>
|
||
<filter name="foo" string="foo" context="{'group_by': 'foo'}"/>
|
||
<filter name="product_id" string="product" context="{'group_by': 'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="bar" string="bar" context="{'group_by': 'bar'}"/>
|
||
<filter name="product_id" string="product" context="{'group_by': 'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="bar" string="bar" context="{'group_by': 'bar'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="bar" string="bar" context="{'group_by': 'bar'}"/>
|
||
<filter name="product_id" string="product" context="{'group_by': 'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" invisible="1"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="product_id" string="product" context="{'group_by': 'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `<pivot/>`,
|
||
});
|
||
|
||
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: `<pivot/>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '=', 12345)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: "<pivot/>",
|
||
});
|
||
|
||
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: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<field name="foo"/>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '>', 10)]"/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('foo', '=', 12345)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter string="Product" name="product_id" context="{'group_by':'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter string="Date" name="date" context="{'group_by':'date'}"/>
|
||
<filter string="Product" name="product_id" context="{'group_by':'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter string="Date" name="date" context="{'group_by':'date'}"/>
|
||
<filter string="Product" name="product_id" context="{'group_by':'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="col"/>
|
||
<field name="amount" type="measure"/>
|
||
</pivot>`,
|
||
context: {
|
||
search_default_initial_context: 1,
|
||
},
|
||
searchViewArch: `
|
||
<search>
|
||
<filter
|
||
name="initial_context"
|
||
string="Initial favorite"
|
||
domain="[]"
|
||
context="{
|
||
'pivot_measures': ['foo'],
|
||
'pivot_column_groupby': ['customer'],
|
||
'pivot_row_groupby': ['product_id'],
|
||
}"
|
||
/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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"] = `<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="group_by_product" string="Product" domain="[]" context="{'group_by': 'product_id'}"/>
|
||
<filter name="group_by_bar" string="Bar" domain="[]" context="{'group_by': 'bar'}"/>
|
||
</search>`;
|
||
|
||
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"] = `<pivot></pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="groubybar" string="Bar" domain="[]" context="{'group_by': 'bar'}"/>
|
||
</search>`;
|
||
|
||
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"] = `<pivot>
|
||
<field name="customer" type="row"/>
|
||
<field name="date" interval="month" type="col" />
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search/>`;
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
context: {
|
||
pivot_measures: ["foo"],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_row_groupby: ["product_id"],
|
||
},
|
||
searchViewArch: `
|
||
<search>
|
||
<filter
|
||
name="no_context_filter"
|
||
string="My fake favorite"
|
||
domain="[]"
|
||
context="{}"
|
||
/>
|
||
<filter
|
||
name="reset_filter"
|
||
string="My fake favorite 2"
|
||
domain="[]"
|
||
context="{
|
||
'pivot_measures': ['foo'],
|
||
'pivot_column_groupby': ['customer'],
|
||
'pivot_row_groupby': ['product_id'],
|
||
}"
|
||
/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter_1" string="My Filter 1" domain="[('product_id', '=', 37)]"/>
|
||
<filter name="my_filter_2" string="My Filter 2" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="company_type" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="dummy_filter" string="Dummy Filter" domain="[('id', '>', 0)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter_1" string="My Filter 1" domain="[('id', '=', 0)]"/>
|
||
<filter name="my_filter_2" string="My Filter 2" domain="[('product_id', '=', 37)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="col"/>
|
||
<field name="amount" type="measure"/>
|
||
</pivot>`,
|
||
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: "<pivot/>",
|
||
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"] = `<pivot/>`;
|
||
Partner._views[
|
||
"search,false"
|
||
] = `<search><filter name="bayou" string="Bayou" domain="[(1,'=',1)]"/></search>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
Partner._views["form,false"] = `<form><field name="foo"/></form>`;
|
||
|
||
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"] = `<pivot/>`;
|
||
Partner._views[
|
||
"search,false"
|
||
] = `<search><filter name="bayou" string="Bayou" domain="[(1,'=',1)]"/></search>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
Partner._views["form,false"] = `<form><field name="foo"/></form>`;
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="col"/>
|
||
<field name="amount" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter
|
||
name="filter_with_context"
|
||
string="My fake favorite"
|
||
domain="[]"
|
||
context="{
|
||
'pivot_measures': ['foo'],
|
||
'pivot_column_groupby': ['customer'],
|
||
'pivot_row_groupby': ['product_id']
|
||
}"
|
||
/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="customer" type="col" />
|
||
<field name="foo" type="measure" />
|
||
</pivot>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="customer" type="col"/>
|
||
<field name="date" interval="day" type="row"/>
|
||
</pivot>`,
|
||
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: "<pivot></pivot>",
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="reference" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter string="Product" name="product_id" context="{'group_by':'product_id'}"/>
|
||
<filter string="Customer" name="customer" context="{'group_by':'customer'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="zip" type="measure"/>
|
||
<field name="foo" type="measure"/>
|
||
<field name="bouh" type="measure"/>
|
||
<field name="modd" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot default_order="foo asc">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='year-1'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month-2'/>
|
||
</search>`,
|
||
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: '<pivot><field name="customer" type="row"/></pivot>',
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="row"/>
|
||
<field name="company_type" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `<pivot><field name="foo" type="measure"/></pivot>`,
|
||
});
|
||
|
||
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"] = `<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="bayou" string="Bayou" domain="[('foo','=', 12)]"/>
|
||
</search>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
Partner._views["form,false"] = `<form><field name="foo"/></form>`;
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="row"/>
|
||
<field name="company_type" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="row"/>
|
||
<field name="company_type" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='month'/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot o_enable_linking="1">
|
||
<field name="date" interval="day" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot o_enable_linking="1">
|
||
<field name="favorite_animal" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="date" type="row" interval="day"/>
|
||
<field name="bar" type="col"/>
|
||
<field name="bar" string="SLA status Failed" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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"] = `<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`;
|
||
|
||
await mountView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
context: { search_default_small_than_0: true },
|
||
noContentHelp: markup(`<p class="abc">click to add a foo</p>`),
|
||
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"] = `<pivot sample="1">
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`;
|
||
|
||
await mountView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
context: { search_default_small_than_0: true },
|
||
noContentHelp: markup('<p class="abc">click to add a foo</p>'),
|
||
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"] = `<pivot sample="1">
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`;
|
||
|
||
await mountView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
noContentHelp: markup('<p class="abc">click to add a foo</p>'),
|
||
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"] = `<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search/>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
|
||
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"] = `<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search/>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
|
||
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"] = `<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search/>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
|
||
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"] = `<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`;
|
||
Partner._views["search,false"] = `<search/>`;
|
||
Partner._views["list,false"] = `<list><field name="foo"/></list>`;
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="date" type="col"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
// 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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="my_filter" string="My Filter" domain="[('product_id', '=', 37)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
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: `<pivot/>`,
|
||
});
|
||
|
||
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: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot display_quantity="1">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: `<pivot/>`,
|
||
});
|
||
|
||
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: `<pivot/>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name='product_id' string="Product" context="{'group_by':'product_id'}"/>
|
||
<filter name='customer' string="Customer" context="{'group_by':'customer'}"/>
|
||
</search>
|
||
`,
|
||
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: `<pivot/>`,
|
||
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: `<pivot/>`,
|
||
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: `<pivot/>`,
|
||
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: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="bar" type="row"/>
|
||
</pivot>
|
||
`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="xphone" domain="[('product_id', '=', 37)]" />
|
||
</search>
|
||
`,
|
||
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: `
|
||
<pivot sample="1">
|
||
<field name="product_id" type="row"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
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: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name='group_by_properties' string="Properties" context="{'group_by':'properties'}"/>
|
||
</search>
|
||
`,
|
||
});
|
||
|
||
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: `
|
||
<pivot sample="1">
|
||
<field name="date" type="row"/>
|
||
<field name="date" type="col" interval="month"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
expect.verifySteps([[], ["date:month"]]);
|
||
});
|
||
|
||
test("Close header dropdown when a simple groupby is selected", async function (assert) {
|
||
await mountView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
arch: `<pivot/>`,
|
||
});
|
||
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: `<pivot/>`,
|
||
});
|
||
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: `<pivot/>`,
|
||
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: `<pivot/>`,
|
||
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");
|
||
});
|