Before this commit, since we are no longer in 2023, the test failed because there is no longer 2021 in the filters dynamically added in the dropdown of the date filter. The reason is now we have the current year, the previous one, and the one before, that is, 2024, 2023, 2022. This commit adds a patchDate to be sure we will start the test in 2023, to get the expected filters displayed (2023, 2022, 2021). closes odoo/odoo#147809 Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
5778 lines
200 KiB
JavaScript
5778 lines
200 KiB
JavaScript
/** @odoo-module **/
|
||
|
||
import { registry } from "@web/core/registry";
|
||
import { session } from "@web/session";
|
||
import { makeFakeLocalizationService, makeFakeUserService } from "../helpers/mock_services";
|
||
import {
|
||
click,
|
||
getFixture,
|
||
makeDeferred,
|
||
mockDownload,
|
||
nextTick,
|
||
patchDate,
|
||
patchWithCleanup,
|
||
triggerEvents,
|
||
mouseEnter,
|
||
getNodesTextContent,
|
||
} from "../helpers/utils";
|
||
import {
|
||
editFavoriteName,
|
||
getFacetTexts,
|
||
removeFacet,
|
||
saveFavorite,
|
||
selectGroup,
|
||
setupControlPanelFavoriteMenuRegistry,
|
||
setupControlPanelServiceRegistry,
|
||
toggleSearchBarMenu,
|
||
toggleMenu,
|
||
toggleMenuItem,
|
||
toggleMenuItemOption,
|
||
toggleSaveFavorite,
|
||
} from "../search/helpers";
|
||
import { createWebClient, doAction } from "../webclient/helpers";
|
||
import { makeView } from "./helpers";
|
||
import { browser } from "@web/core/browser/browser";
|
||
import { _t } from "@web/core/l10n/translation";
|
||
|
||
const serviceRegistry = registry.category("services");
|
||
|
||
import { markup } from "@odoo/owl";
|
||
|
||
/**
|
||
* Helper function that returns, given a pivot instance, the values of the
|
||
* table, separated by ','.
|
||
*
|
||
* @returns {string}
|
||
*/
|
||
function getCurrentValues(el) {
|
||
return [...el.querySelectorAll(".o_pivot_cell_value div")].map((el) => el.innerText).join();
|
||
}
|
||
|
||
let serverData;
|
||
let target;
|
||
QUnit.module("Views", (hooks) => {
|
||
hooks.beforeEach(() => {
|
||
target = getFixture();
|
||
serverData = {
|
||
models: {
|
||
partner: {
|
||
fields: {
|
||
foo: {
|
||
string: "Foo",
|
||
type: "integer",
|
||
searchable: true,
|
||
group_operator: "sum",
|
||
},
|
||
bar: { string: "bar", type: "boolean", store: true, sortable: true },
|
||
date: { string: "Date", type: "date", store: true, sortable: true },
|
||
product_id: {
|
||
string: "Product",
|
||
type: "many2one",
|
||
relation: "product",
|
||
store: true,
|
||
sortable: true,
|
||
},
|
||
other_product_id: {
|
||
string: "Other Product",
|
||
type: "many2one",
|
||
relation: "product",
|
||
store: true,
|
||
sortable: true,
|
||
},
|
||
non_stored_m2o: {
|
||
string: "Non Stored M2O",
|
||
type: "many2one",
|
||
relation: "product",
|
||
},
|
||
customer: {
|
||
string: "Customer",
|
||
type: "many2one",
|
||
relation: "customer",
|
||
store: true,
|
||
sortable: true,
|
||
},
|
||
computed_field: {
|
||
string: "Computed and not stored",
|
||
type: "integer",
|
||
compute: true,
|
||
group_operator: "sum",
|
||
},
|
||
company_type: {
|
||
string: "Company Type",
|
||
type: "selection",
|
||
selection: [
|
||
["company", "Company"],
|
||
["individual", "individual"],
|
||
],
|
||
searchable: true,
|
||
sortable: true,
|
||
store: true,
|
||
},
|
||
price_nonaggregatable: {
|
||
string: "Price non-aggregatable",
|
||
type: "monetary",
|
||
group_operator: undefined,
|
||
store: true,
|
||
},
|
||
ref: {
|
||
string: "Reference",
|
||
type: "reference",
|
||
selection: [
|
||
["product", "Product"],
|
||
["customer", "Customer"],
|
||
],
|
||
group_operator: "count_distinct",
|
||
},
|
||
properties: {
|
||
string: "Properties",
|
||
type: "properties",
|
||
definition_record: "parent_id",
|
||
definition_record_field: "properties_definition",
|
||
name: "properties",
|
||
},
|
||
parent_id: {
|
||
string: "Parent",
|
||
type: "many2one",
|
||
relation: "partner",
|
||
name: "parent_id",
|
||
},
|
||
properties_definition: {
|
||
string: "Properties",
|
||
type: "properties_definition",
|
||
},
|
||
},
|
||
records: [
|
||
{
|
||
id: 1,
|
||
foo: 12,
|
||
bar: true,
|
||
date: "2016-12-14",
|
||
product_id: 37,
|
||
customer: 1,
|
||
computed_field: 19,
|
||
company_type: "company",
|
||
ref: "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,
|
||
computed_field: 23,
|
||
company_type: "individual",
|
||
ref: "product,41",
|
||
parent_id: 1,
|
||
properties: [
|
||
{
|
||
name: "my_char",
|
||
string: "My Char",
|
||
type: "char",
|
||
value: "aaa",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: 3,
|
||
foo: 17,
|
||
bar: true,
|
||
date: "2016-12-15",
|
||
product_id: 41,
|
||
customer: 2,
|
||
computed_field: 26,
|
||
company_type: "company",
|
||
ref: "customer,1",
|
||
parent_id: 1,
|
||
properties: [
|
||
{
|
||
name: "my_char",
|
||
string: "My Char",
|
||
type: "char",
|
||
value: "bbb",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: 4,
|
||
foo: 2,
|
||
bar: false,
|
||
date: "2016-04-11",
|
||
product_id: 41,
|
||
customer: 1,
|
||
computed_field: 19,
|
||
company_type: "individual",
|
||
ref: "customer,2",
|
||
},
|
||
],
|
||
},
|
||
product: {
|
||
fields: {
|
||
name: { string: "Product Name", type: "char" },
|
||
},
|
||
records: [
|
||
{
|
||
id: 37,
|
||
display_name: "xphone",
|
||
},
|
||
{
|
||
id: 41,
|
||
display_name: "xpad",
|
||
},
|
||
],
|
||
},
|
||
customer: {
|
||
fields: {
|
||
name: { string: "Customer Name", type: "char" },
|
||
},
|
||
records: [
|
||
{
|
||
id: 1,
|
||
display_name: "First",
|
||
},
|
||
{
|
||
id: 2,
|
||
display_name: "Second",
|
||
},
|
||
],
|
||
},
|
||
},
|
||
};
|
||
setupControlPanelFavoriteMenuRegistry();
|
||
setupControlPanelServiceRegistry();
|
||
serviceRegistry.add("localization", makeFakeLocalizationService());
|
||
serviceRegistry.add("user", makeFakeUserService());
|
||
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
|
||
});
|
||
|
||
QUnit.module("PivotView");
|
||
|
||
QUnit.test("simple pivot rendering", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
assert.strictEqual(
|
||
args.kwargs.lazy,
|
||
false,
|
||
"the read_group should be done with the lazy=false option"
|
||
);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.hasClass(target.querySelector(".o_pivot_view"), "o_view_controller");
|
||
assert.hasClass(target.querySelector("table"), "o_enable_linking");
|
||
assert.containsOnce(
|
||
target,
|
||
"td.o_pivot_cell_value:contains(32)",
|
||
"should contain a pivot cell with the sum of all records"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"all measures should be displayed with a pivot_measures context",
|
||
async function (assert) {
|
||
assert.expect(1);
|
||
|
||
serverData.models.partner.fields.bouh = {
|
||
string: "bouh",
|
||
type: "integer",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
context: { pivot_measures: ["foo"] },
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="bouh" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
const measures = Array.from(
|
||
target.querySelectorAll(".o_pivot_buttons .dropdown-menu .dropdown-item")
|
||
).map((e) => e.textContent);
|
||
|
||
assert.deepEqual(measures, ["bouh", "Foo", "Count"]);
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot rendering with widget", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure" widget="float_time"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"td.o_pivot_cell_value:contains(32:00)",
|
||
"should contain a pivot cell with the sum of all records"
|
||
);
|
||
});
|
||
|
||
QUnit.test("pivot rendering with string attribute on field", async function (assert) {
|
||
serverData.models.partner.fields.foo = {
|
||
string: "Foo",
|
||
type: "integer",
|
||
store: true,
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" string="BAR" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_buttons .dropdown-menu .dropdown-item").innerText,
|
||
"BAR"
|
||
);
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_measure_row").innerText,
|
||
"BAR",
|
||
"the displayed name should be the one set in the string attribute"
|
||
);
|
||
});
|
||
|
||
QUnit.test("Pivot with integer row group by with 0 as header", async function (assert) {
|
||
serverData.models.partner.records[0].foo = 0;
|
||
serverData.models.partner.records[1].foo = 0;
|
||
serverData.models.partner.records[2].foo = 0;
|
||
serverData.models.partner.records[3].foo = 0;
|
||
|
||
const pivot = await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="foo" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
const { rows } = pivot.model.getTable();
|
||
assert.strictEqual(rows.length, 2);
|
||
assert.strictEqual(rows[0].title, "Total");
|
||
assert.strictEqual(rows[1].title, 0);
|
||
});
|
||
|
||
QUnit.test("Pivot with integer col group by with 0 as header", async function (assert) {
|
||
serverData.models.partner.records[0].foo = 0;
|
||
serverData.models.partner.records[1].foo = 0;
|
||
serverData.models.partner.records[2].foo = 0;
|
||
serverData.models.partner.records[3].foo = 0;
|
||
|
||
const pivot = await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="measure"/>
|
||
<field name="foo" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
const { headers } = pivot.model.getTable();
|
||
assert.strictEqual(headers[1][0].title, 0);
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot rendering with string attribute on non stored field",
|
||
async function (assert) {
|
||
serverData.models.partner.fields.fubar = {
|
||
string: "Fubar",
|
||
type: "integer",
|
||
store: false,
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="fubar" string="fubar" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_measure_row").innerText,
|
||
"fubar",
|
||
"the displayed name should be the one set in the string attribute"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot rendering with invisible attribute on field", async function (assert) {
|
||
// when invisible, a field should neither be an active measure nor be a selectable measure
|
||
Object.assign(serverData.models.partner.fields, {
|
||
foo: { string: "Foo", type: "integer", store: true, group_operator: "sum" },
|
||
foo2: { string: "Foo2", type: "integer", store: true, group_operator: "sum" },
|
||
});
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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
|
||
assert.containsOnce(target, ".o_pivot_measure_row");
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
// there should be only one measure besides count, as the other one is invisible
|
||
assert.containsN(target, ".o_pivot_buttons .dropdown-menu .dropdown-item", 2);
|
||
// the invisible field souldn't be in the groupable fields neither
|
||
await click(target.querySelector(".o_pivot_header_cell_closed"));
|
||
assert.containsNone(target, '.dropdown-menu a[data-field="foo2"]');
|
||
});
|
||
|
||
QUnit.test('pivot view without "string" attribute', async function (assert) {
|
||
assert.expect(1);
|
||
|
||
const pivot = await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// this is important for export functionality.
|
||
assert.strictEqual(
|
||
pivot.model.metaData.title.toString(),
|
||
_t("Untitled"),
|
||
"should have a valid title"
|
||
);
|
||
});
|
||
|
||
QUnit.test("group headers should have a tooltip", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="date" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelectorAll("tbody .o_pivot_header_cell_closed")[0].dataset.tooltip,
|
||
"Date"
|
||
);
|
||
assert.strictEqual(
|
||
target.querySelectorAll("thead .o_pivot_header_cell_closed")[1].dataset.tooltip,
|
||
"Product"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot view add computed fields explicitly defined as measure",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="computed_field" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.containsOnce(
|
||
target,
|
||
".o_pivot_buttons .dropdown-menu .dropdown-item:contains(Computed and not stored)"
|
||
);
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_measure_row").innerText,
|
||
"Computed and not stored"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"pivot view do not add number field without group_operator",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="price_nonaggregatable"/>
|
||
</pivot>`,
|
||
});
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.containsNone(
|
||
target,
|
||
".o_cp_bottom_left .dropdown-menu .dropdown-item:contains(Price non-aggregatable)"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("clicking on a cell triggers a doAction", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
serverData.views = {
|
||
"partner,2,form": "<form/>",
|
||
"partner,false,list": "<list/>",
|
||
"partner,5,kanban": "<kanban/>",
|
||
};
|
||
patchWithCleanup(session, {
|
||
user_context: { userContextKey: true },
|
||
});
|
||
const fakeActionService = {
|
||
start() {
|
||
return {
|
||
doAction(action) {
|
||
assert.deepEqual(
|
||
action,
|
||
{
|
||
context: { someKey: true, uid: 7, userContextKey: true },
|
||
domain: [["product_id", "=", 37]],
|
||
name: "Partners",
|
||
res_model: "partner",
|
||
target: "current",
|
||
type: "ir.actions.act_window",
|
||
view_mode: "list",
|
||
views: [
|
||
[false, "list"],
|
||
[2, "form"],
|
||
],
|
||
},
|
||
"should trigger do_action with the correct args"
|
||
);
|
||
return Promise.resolve(true);
|
||
},
|
||
};
|
||
},
|
||
};
|
||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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"],
|
||
],
|
||
},
|
||
});
|
||
|
||
assert.hasClass(target.querySelector("table"), "o_enable_linking");
|
||
await click(target.querySelectorAll(".o_pivot_cell_value")[1]); // should trigger a do_action
|
||
});
|
||
|
||
QUnit.test("row and column are highlighted when hovering a cell", async function (assert) {
|
||
assert.expect(11);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot string="Partners">
|
||
<field name="foo" type="col"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// check row highlighting
|
||
assert.hasClass(
|
||
target.querySelector("table"),
|
||
"table-hover",
|
||
"with className 'table-hover', rows are highlighted (bootstrap)"
|
||
);
|
||
|
||
// check column highlighting
|
||
// hover third measure
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(3)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (var i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(3)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(3)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
|
||
// hover second cell, second row
|
||
await triggerEvents(target, "tbody tr:nth-of-type(1) td:nth-of-type(2)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(2)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "tbody tr:nth-of-type(2) td:nth-of-type(2)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
});
|
||
|
||
QUnit.test("columns are highlighted when hovering a measure", async function (assert) {
|
||
assert.expect(15);
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
serverData.models.partner.records[0].date = "2016-11-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: true },
|
||
});
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
// hover Count in first group
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(1)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (let i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(1)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(1)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
|
||
// hover Count in second group
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(2)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (let i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(4)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(2)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
|
||
// hover Count in total column
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(3)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (let i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(7)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "th.o_pivot_measure_row:nth-of-type(3)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
});
|
||
|
||
QUnit.test(
|
||
"columns are highlighted when hovering an origin (comparison mode)",
|
||
async function (assert) {
|
||
assert.expect(5);
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
serverData.models.partner.records[0].date = "2016-11-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: true },
|
||
});
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
// hover the second origin in second group
|
||
await triggerEvents(target, "th.o_pivot_origin_row:nth-of-type(5)", ["mouseover"]);
|
||
assert.containsN(target, ".o_cell_hover", 3);
|
||
for (let i = 1; i <= 3; i++) {
|
||
assert.hasClass(
|
||
target.querySelector(`tbody tr:nth-of-type(${i}) td:nth-of-type(5)`),
|
||
"o_cell_hover"
|
||
);
|
||
}
|
||
await triggerEvents(target, "th.o_pivot_origin_row:nth-of-type(5)", ["mouseout"]);
|
||
assert.containsNone(target, ".o_cell_hover");
|
||
}
|
||
);
|
||
|
||
QUnit.test('pivot view with disable_linking="True"', async function (assert) {
|
||
const fakeActionService = {
|
||
start() {
|
||
return {
|
||
doAction() {
|
||
throw new Error("should not execute an action");
|
||
},
|
||
};
|
||
},
|
||
};
|
||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot disable_linking="True">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.doesNotHaveClass(target.querySelector("table"), "o_enable_linking");
|
||
assert.containsOnce(target, ".o_pivot_cell_value");
|
||
await click(target.querySelector(".o_pivot_cell_value")); // should not trigger a do_action
|
||
});
|
||
|
||
QUnit.test('clicking on the "Total" cell with time range activated', async function (assert) {
|
||
assert.expect(2);
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
const fakeActionService = {
|
||
start() {
|
||
return {
|
||
doAction(action) {
|
||
assert.deepEqual(
|
||
action.domain,
|
||
["&", ["date", ">=", "2016-12-01"], ["date", "<=", "2016-12-31"]],
|
||
"should trigger do_action with the correct action domain"
|
||
);
|
||
return Promise.resolve(true);
|
||
},
|
||
};
|
||
},
|
||
};
|
||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: true },
|
||
});
|
||
|
||
assert.hasClass(
|
||
target.querySelector("table"),
|
||
"o_enable_linking",
|
||
"root node should have classname 'o_enable_linking'"
|
||
);
|
||
await click(target.querySelector(".o_pivot_cell_value"));
|
||
});
|
||
|
||
QUnit.test(
|
||
'clicking on a fake cell value ("empty group") in comparison mode',
|
||
async function (assert) {
|
||
assert.expect(3);
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
serverData.models.partner.records[0].date = "2016-11-15";
|
||
serverData.models.partner.records[1].date = "2016-11-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
const expectedDomains = [
|
||
["&", ["date", ">=", "2016-12-01"], ["date", "<=", "2016-12-31"]],
|
||
[[0, "=", 1]],
|
||
];
|
||
const fakeActionService = {
|
||
start() {
|
||
return {
|
||
doAction(action) {
|
||
assert.deepEqual(action.domain, expectedDomains.shift());
|
||
return Promise.resolve(true);
|
||
},
|
||
};
|
||
},
|
||
};
|
||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot><field name="product_id" type="row"/></pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: true },
|
||
});
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
assert.hasClass(target.querySelector("table"), "o_enable_linking");
|
||
// here we click on the group corresponding to Total/Total/This Month
|
||
target.querySelectorAll(".o_pivot_cell_value")[1].click(); // should trigger a do_action with appropriate domain
|
||
// here we click on the group corresponding to xphone/Total/This Month
|
||
target.querySelectorAll(".o_pivot_cell_value")[4].click(); // should trigger a do_action with appropriate domain
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot view grouped by date field", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
const wrongFields = args.kwargs.fields.filter((field) => {
|
||
return !(field.split(":")[0] in serverData.models.partner.fields);
|
||
});
|
||
assert.ok(
|
||
!wrongFields.length,
|
||
"fields given to read_group should exist on the model"
|
||
);
|
||
}
|
||
},
|
||
});
|
||
});
|
||
|
||
QUnit.test("without measures, pivot view uses __count by default", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot></pivot>",
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
assert.deepEqual(
|
||
args.kwargs.fields,
|
||
["__count"],
|
||
"should make a read_group with no valid fields"
|
||
);
|
||
}
|
||
},
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons .dropdown-toggle"));
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-menu .dropdown-item");
|
||
const measure = target.querySelector(".o_pivot_buttons .dropdown-menu .dropdown-item");
|
||
assert.strictEqual(measure.innerText, "Count");
|
||
assert.hasClass(measure, "selected", "The count measure should be selected");
|
||
});
|
||
|
||
QUnit.test("pivot view can be reloaded", async function (assert) {
|
||
let readGroupCount = 0;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot></pivot>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '>', 10)]"/>
|
||
</search>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
readGroupCount++;
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"td.o_pivot_cell_value:contains(4)",
|
||
"should contain a pivot cell with the number of all records"
|
||
);
|
||
assert.strictEqual(readGroupCount, 1, "should have done 1 rpc");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Some Filter");
|
||
assert.containsOnce(
|
||
target,
|
||
"td.o_pivot_cell_value:contains(2)",
|
||
"should contain a pivot cell with the number of remaining records"
|
||
);
|
||
assert.strictEqual(readGroupCount, 2, "should have done 2 rpcs");
|
||
});
|
||
|
||
QUnit.test("pivot view grouped by many2one field", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_header_cell_opened", "should have one opened header");
|
||
assert.containsOnce(
|
||
target,
|
||
".o_pivot_header_cell_closed:contains(xphone)",
|
||
"should display one header with 'xphone'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".o_pivot_header_cell_closed:contains(xpad)",
|
||
"should display one header with 'xpad'"
|
||
);
|
||
});
|
||
|
||
QUnit.test("basic folding/unfolding", async function (assert) {
|
||
assert.expect(7);
|
||
|
||
let rpcCount = 0;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
rpcCount++;
|
||
}
|
||
},
|
||
});
|
||
assert.containsN(
|
||
target,
|
||
"tbody tr",
|
||
3,
|
||
"should have 3 rows: 1 for the opened header, and 2 for data"
|
||
);
|
||
|
||
// click on the opened header to close it
|
||
await click(target, ".o_pivot_header_cell_opened");
|
||
|
||
assert.containsOnce(target, "tbody tr", "should have 1 row");
|
||
|
||
// click on closed header to open dropdown
|
||
await click(target, "tbody .o_pivot_header_cell_closed");
|
||
assert.containsOnce(target, ".o_pivot .dropdown-menu");
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot .dropdown-menu").innerText.replace(/\s/g, ""),
|
||
"CompanyTypeCustomerDateOtherProductProductbarAddCustomGroupCompanyTypeCustomerDateOtherProductProductbar"
|
||
);
|
||
|
||
// open the Date sub dropdown
|
||
await mouseEnter(target, ".o_pivot .dropdown-menu .dropdown-toggle.o_menu_item");
|
||
assert.strictEqual(
|
||
target
|
||
.querySelector(".o_pivot .dropdown-menu .dropdown-menu")
|
||
.innerText.replace(/\s/g, ""),
|
||
"YearQuarterMonthWeekDay"
|
||
);
|
||
|
||
await click(
|
||
target.querySelectorAll(".o_pivot .dropdown-menu .dropdown-menu .dropdown-item")[2]
|
||
);
|
||
|
||
assert.containsN(target, "tbody tr", 4, "should have 4 rows: one for header, 3 for data");
|
||
assert.strictEqual(
|
||
rpcCount,
|
||
3,
|
||
"should have done 3 rpcs (initial load) + open header with different groupbys"
|
||
);
|
||
});
|
||
|
||
QUnit.test("more folding/unfolding", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// open dropdown to zoom into first row
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
// click on date by day
|
||
await mouseEnter(target.querySelector("tbody .dropdown-menu .dropdown-toggle"));
|
||
await click(target.querySelector("tbody .dropdown-menu .dropdown-menu span:nth-child(5)"));
|
||
|
||
// open dropdown to zoom into second row
|
||
await click(target.querySelectorAll("tbody th.o_pivot_header_cell_closed")[1]);
|
||
|
||
assert.containsN(
|
||
target,
|
||
"tbody tr",
|
||
7,
|
||
"should have 7 rows (1 for total, 1 for xphone, 1 for xpad, 4 for data)"
|
||
);
|
||
});
|
||
|
||
QUnit.test("fold and unfold header group", async function (assert) {
|
||
assert.expect(3);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.containsN(target, "thead tr", 3);
|
||
|
||
// fold opened col group
|
||
await click(target.querySelector("thead .o_pivot_header_cell_opened"));
|
||
assert.containsN(target, "thead tr", 2);
|
||
|
||
// unfold it
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelector(".dropdown-menu span:nth-child(5)"));
|
||
assert.containsN(target, "thead tr", 3);
|
||
});
|
||
|
||
QUnit.test("unfold second header group", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.containsN(target, "thead tr", 3);
|
||
let values = ["12", "20", "32"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// unfold it
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed:last-child"));
|
||
await click(target.querySelector(".dropdown-menu span:nth-child(1)"));
|
||
assert.containsN(target, "thead tr", 4);
|
||
values = ["12", "17", "3", "32"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot renders group dropdown same as search groupby dropdown if group bys are specified in search arch",
|
||
async function (assert) {
|
||
assert.expect(6);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
6,
|
||
"should have 6 menu items in searchview"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_add_custom_group_menu",
|
||
"should have custom group generator in searchview groupby"
|
||
);
|
||
|
||
// click on closed header to open dropdown
|
||
await click(target, "tbody tr:last-child .o_pivot_header_cell_closed");
|
||
assert.containsN(
|
||
target,
|
||
".dropdown-menu > .dropdown-item",
|
||
4,
|
||
"should have 4 dropdown items same as searchview groupby"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".dropdown-menu .o_add_custom_group_menu",
|
||
"should have custom group generator same as searchview groupby"
|
||
);
|
||
// check custom groupby selection has groupable fields only
|
||
assert.containsN(
|
||
target,
|
||
".o_add_custom_group_menu option:not([disabled])",
|
||
6,
|
||
"should have 6 fields in custom groupby"
|
||
);
|
||
const optionDescriptions = [
|
||
...target.querySelectorAll(".o_add_custom_group_menu option:not([disabled])"),
|
||
].map((option) => option.innerText.trim());
|
||
assert.deepEqual(
|
||
optionDescriptions,
|
||
["Company Type", "Customer", "Date", "Other Product", "Product", "bar"],
|
||
"should only have groupable fields in custom groupby"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot group dropdown sync with search groupby dropdown", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
5,
|
||
"should have 5 menu items in searchview"
|
||
);
|
||
|
||
// click on closed header to open dropdown
|
||
await click(target, "tbody tr:last-child .o_pivot_header_cell_closed");
|
||
assert.containsN(
|
||
target,
|
||
".dropdown-menu .o_menu_item",
|
||
3,
|
||
"should have 3 menu items in pivot groupby"
|
||
);
|
||
|
||
// add a custom group in searchview groupby
|
||
await toggleSearchBarMenu(target);
|
||
await selectGroup(target, "company_type");
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
6,
|
||
"should have 6 menu items in searchview"
|
||
);
|
||
await click(target, "tbody tr:last-child .o_pivot_header_cell_closed");
|
||
assert.containsN(
|
||
target,
|
||
".dropdown-menu .o_menu_item",
|
||
3,
|
||
"should still have 3 menu items in pivot groupby"
|
||
);
|
||
|
||
// add a custom group in pivot groupby
|
||
await selectGroup(target, "date");
|
||
// click on closed header to open groupby selection dropdown
|
||
await click(target, "tbody tr:last-child .o_pivot_header_cell_closed");
|
||
assert.containsN(
|
||
target,
|
||
".dropdown-menu .o_menu_item",
|
||
4,
|
||
"should have 4 menu items in pivot groupby dropdown"
|
||
);
|
||
|
||
// applying custom groupby in pivot groupby dropdown will not update search dropdown
|
||
await toggleSearchBarMenu(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
6,
|
||
"should still have 6 menu items in searchview"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot groupby dropdown renders custom search at the end with separator",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
5,
|
||
"should have 5 menu items in searchview"
|
||
);
|
||
await selectGroup(target, "company_type");
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
6,
|
||
"should have 6 menu items in searchview"
|
||
);
|
||
|
||
// click on closed header to open dropdown
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
|
||
let items = target.querySelectorAll(".o_menu_item:not(select)");
|
||
assert.deepEqual(getNodesTextContent(items), ["bar", "product"]);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .dropdown-menu .dropdown-divider",
|
||
"pivot groupby menu should only have one separator"
|
||
);
|
||
assert.hasClass(
|
||
items[items.length - 1].nextElementSibling,
|
||
"dropdown-divider",
|
||
"pivot groupby menu separator is placed after all menu items"
|
||
);
|
||
|
||
// add a custom group in pivot groupby
|
||
await selectGroup(target, "customer");
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
|
||
items = target.querySelectorAll(".o_menu_item:not(select)");
|
||
assert.deepEqual(getNodesTextContent(items), ["bar", "product", "Customer"]);
|
||
assert.containsN(
|
||
target,
|
||
"tbody .dropdown-menu .dropdown-divider",
|
||
2,
|
||
"pivot groupby menu should now have two separators"
|
||
);
|
||
assert.hasClass(
|
||
items[items.length - 1].previousElementSibling,
|
||
"dropdown-divider",
|
||
"last pivot groupby menu item is placed after a separator"
|
||
);
|
||
assert.hasClass(
|
||
items[items.length - 1].nextElementSibling,
|
||
"dropdown-divider",
|
||
"a pivot groupby menu separator is placed after all menu items"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"pivot custom groupby: grouping on date field use default interval month",
|
||
async function (assert) {
|
||
assert.expect(1);
|
||
|
||
let checkReadGroup = false;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group" && checkReadGroup) {
|
||
assert.deepEqual(
|
||
args.kwargs.groupby,
|
||
["date:month"],
|
||
"should use default month as an interval in read_group"
|
||
);
|
||
checkReadGroup = false;
|
||
}
|
||
},
|
||
});
|
||
|
||
// click on closed header to open dropdown and apply groupby on date field
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
|
||
checkReadGroup = true;
|
||
await selectGroup(target, "date");
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot view without group by specified in search arch", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// open group by dropdown
|
||
await toggleSearchBarMenu(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
3,
|
||
"should not have more menu items than default in searchview"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_add_custom_group_menu",
|
||
"should have add custom group item in searchview groupby"
|
||
);
|
||
// click on closed header to open dropdown
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
|
||
assert.containsN(
|
||
target,
|
||
"tbody .dropdown-menu .o_menu_item",
|
||
7,
|
||
"should have 7 menu items i.e. all groupable fields available"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".dropdown-menu .o_add_custom_group_menu",
|
||
"should have custom group generator in groupby dropdown"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot view do not show custom group selection if there are no groupable fields",
|
||
async function (assert) {
|
||
assert.expect(4);
|
||
|
||
for (const fieldName of [
|
||
"bar",
|
||
"company_type",
|
||
"customer",
|
||
"date",
|
||
"other_product_id",
|
||
]) {
|
||
delete serverData.models.partner.fields[fieldName];
|
||
}
|
||
|
||
// Keep product_id but make it ungroupable
|
||
delete serverData.models.partner.fields.product_id.sortable;
|
||
delete serverData.models.partner.fields.product_id.store;
|
||
|
||
serverData.models.partner.records = [
|
||
{
|
||
id: 1,
|
||
foo: 12,
|
||
product_id: 37,
|
||
computed_field: 19,
|
||
},
|
||
];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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(target);
|
||
assert.containsN(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_menu_item",
|
||
3,
|
||
"should have 3 menu item in searchview"
|
||
);
|
||
assert.containsNone(
|
||
target,
|
||
".o_control_panel .o_cp_searchview .dropdown-menu .o_add_custom_group_menu",
|
||
"should not have custom group generator in searchview groupby"
|
||
);
|
||
|
||
// click on closed header to open dropdown
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .dropdown-menu .dropdown-item",
|
||
"should have 1 dropdown items"
|
||
);
|
||
assert.containsNone(
|
||
target,
|
||
".dropdown-menu .o_add_custom_group_menu",
|
||
"should not have custom group generator in groupby dropdown"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("can toggle extra measure", async function (assert) {
|
||
let rpcCount = 0;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC() {
|
||
rpcCount++;
|
||
},
|
||
});
|
||
|
||
rpcCount = 0;
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_cell_value",
|
||
3,
|
||
"should have 3 cells: 1 for the open header, and 2 for data"
|
||
);
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.doesNotHaveClass(
|
||
$(target).find(".dropdown-item:contains(Count)"),
|
||
"selected",
|
||
"the __count measure should not be selected"
|
||
);
|
||
await click($(target).find(".o_pivot_buttons .dropdown-item:contains(Count)")[0]);
|
||
|
||
assert.hasClass(
|
||
$(target).find(".o_pivot_buttons .dropdown-item:contains(Count)"),
|
||
"selected",
|
||
"the __count measure should be selected"
|
||
);
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_cell_value",
|
||
6,
|
||
"should have 6 cells: 2 for the open header, and 4 for data"
|
||
);
|
||
assert.strictEqual(rpcCount, 2, "should have done 2 rpcs to reload data");
|
||
|
||
await click($(target).find(".o_pivot_buttons .dropdown-item:contains(Count)")[0]);
|
||
|
||
assert.doesNotHaveClass(
|
||
$(target).find(".dropdown-item:contains(Count)")[0],
|
||
"selected",
|
||
"the __count measure should not be selected"
|
||
);
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_cell_value",
|
||
3,
|
||
"should have 3 cells: 1 for the open header, and 2 for data"
|
||
);
|
||
assert.strictEqual(rpcCount, 2, "should not have done any extra rpcs");
|
||
});
|
||
|
||
QUnit.test("no content helper when no active measure", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot/>`,
|
||
});
|
||
|
||
assert.containsNone(target, ".o_view_nocontent");
|
||
assert.containsOnce(target, "table");
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
await click($(target).find(".o_pivot_buttons .dropdown-item:contains(Count)")[0]);
|
||
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
assert.containsNone(target, "table");
|
||
});
|
||
|
||
QUnit.test("no content helper when no data", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot/>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '=', 12345)]"/>
|
||
</search>`,
|
||
});
|
||
|
||
assert.containsNone(target, ".o_view_nocontent");
|
||
assert.containsOnce(target, "table");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Some Filter");
|
||
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
assert.containsNone(target, "table");
|
||
});
|
||
|
||
QUnit.test("no content helper when no data, part 2", async function (assert) {
|
||
serverData.models.partner.records = [];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot/>",
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
});
|
||
|
||
QUnit.test("no content helper when no data, part 3", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<field name="foo"/>
|
||
<filter name="some_filter" string="Some Filter" domain="[('foo', '>', 10)]"/>
|
||
</search>`,
|
||
context: {
|
||
search_default_foo: 12345,
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_searchview .o_searchview_facet");
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Some Filter");
|
||
assert.containsN(target, ".o_searchview .o_searchview_facet", 2);
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
|
||
await toggleMenuItem(target, "Some Filter");
|
||
assert.containsOnce(target, ".o_searchview .o_searchview_facet");
|
||
assert.containsOnce(target, ".o_view_nocontent");
|
||
|
||
await click(target, ".o_facet_remove");
|
||
assert.containsNone(target, ".o_searchview .o_searchview_facet");
|
||
assert.containsNone(target, ".o_view_nocontent");
|
||
|
||
// tries to open a field selection menu, to make sure it was not
|
||
// removed from the dom.
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
assert.containsOnce(target, "tbody .dropdown-menu");
|
||
});
|
||
|
||
QUnit.test("tries to restore previous state after domain change", async function (assert) {
|
||
assert.expect(7);
|
||
|
||
let rpcCount = 0;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC() {
|
||
rpcCount++;
|
||
},
|
||
});
|
||
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_cell_value",
|
||
3,
|
||
"should have 3 cells: 1 for the open header, and 2 for data"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".o_pivot_measure_row:contains(Foo)",
|
||
"should have 1 row for measure Foo"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.containsNone(target, "table");
|
||
|
||
rpcCount = 0;
|
||
await removeFacet(target);
|
||
|
||
assert.containsOnce(target, "table");
|
||
assert.equal(rpcCount, 2, "should have reloaded data");
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_cell_value",
|
||
3,
|
||
"should still have 3 cells: 1 for the open header, and 2 for data"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
".o_pivot_measure_row:contains(Foo)",
|
||
"should still have 1 row for measure Foo"
|
||
);
|
||
});
|
||
|
||
QUnit.test("can be grouped with the search view", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter string="Product" name="product_id" context="{'group_by':'product_id'}"/>
|
||
</search>`,
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_cell_value", "should have only 1 cell");
|
||
assert.containsOnce(target, "tbody tr", "should have 1 rows");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Product");
|
||
|
||
assert.containsN(target, ".o_pivot_cell_value", 3, "should have 3 cells");
|
||
assert.containsN(target, "tbody tr", 3, "should have 3 rows");
|
||
});
|
||
|
||
QUnit.test("can sort data in a column by clicking on header", async function (assert) {
|
||
assert.expect(3);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
let values = ["32", "12", "20"];
|
||
assert.strictEqual(
|
||
getCurrentValues(target),
|
||
values.join(","),
|
||
"should have proper values in cells (total, result 1, result 2)"
|
||
);
|
||
|
||
await click(target.querySelector("th.o_pivot_measure_row"));
|
||
|
||
values = ["32", "12", "20"];
|
||
assert.strictEqual(
|
||
getCurrentValues(target),
|
||
values.join(","),
|
||
"should have proper values in cells (total, result 1, result 2)"
|
||
);
|
||
|
||
await click(target.querySelector("th.o_pivot_measure_row"));
|
||
|
||
values = ["32", "20", "12"];
|
||
assert.strictEqual(
|
||
getCurrentValues(target),
|
||
values.join(","),
|
||
"should have proper values in cells (total, result 1, result 2)"
|
||
);
|
||
});
|
||
|
||
QUnit.test("can expand all rows", async function (assert) {
|
||
assert.expect(7);
|
||
|
||
let nbReadGroups = 0;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
nbReadGroups++;
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(nbReadGroups, 2, "should have done 2 read_group RPCS");
|
||
assert.strictEqual(
|
||
getCurrentValues(target),
|
||
"32,12,20",
|
||
"should have proper values in cells (total, result 1, result 2)"
|
||
);
|
||
|
||
// expand on date:days, product
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "Month");
|
||
nbReadGroups = 0;
|
||
await toggleMenuItem(target, "Product");
|
||
|
||
assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS");
|
||
assert.containsN(
|
||
target,
|
||
"tbody tr",
|
||
8,
|
||
"should have 7 rows (total + 3 for December and 2 for October and April)"
|
||
);
|
||
|
||
// collapse the first two rows
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_opened")[2]);
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_opened")[1]);
|
||
|
||
assert.containsN(target, "tbody tr", 6, "should have 6 rows now");
|
||
|
||
// expand all
|
||
nbReadGroups = 0;
|
||
await click(target.querySelector(".o_pivot_expand_button"));
|
||
|
||
assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS");
|
||
assert.containsN(target, "tbody tr", 8, "should have 8 rows again");
|
||
});
|
||
|
||
QUnit.test("expand all with a delay", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
// expand on date:days, product
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "Month");
|
||
await toggleMenuItem(target, "Product");
|
||
assert.containsN(
|
||
target,
|
||
"tbody tr",
|
||
8,
|
||
"should have 7 rows (total + 3 for December and 2 for October and April)"
|
||
);
|
||
|
||
// collapse the first two rows
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_opened")[2]);
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_opened")[1]);
|
||
|
||
assert.containsN(target, "tbody tr", 6, "should have 6 rows now");
|
||
|
||
// expand all
|
||
def = makeDeferred();
|
||
await click(target.querySelector(".o_pivot_expand_button"));
|
||
assert.containsN(target, "tbody tr", 6, "should have 6 rows now");
|
||
def.resolve();
|
||
await nextTick();
|
||
assert.containsN(target, "tbody tr", 8, "should have 8 rows again");
|
||
});
|
||
|
||
QUnit.test("can download a file", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
mockDownload(({ url }) => {
|
||
assert.strictEqual(url, "/web/pivot/export_xlsx");
|
||
return Promise.resolve();
|
||
});
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_download"));
|
||
});
|
||
|
||
QUnit.test(
|
||
"download a file with single measure, measure row displayed in table",
|
||
async function (assert) {
|
||
assert.expect(2);
|
||
|
||
mockDownload(({ url, data }) => {
|
||
data = JSON.parse(data.data);
|
||
assert.strictEqual(url, "/web/pivot/export_xlsx");
|
||
assert.strictEqual(
|
||
data.measure_headers.length,
|
||
4,
|
||
"should have measure_headers in data"
|
||
);
|
||
return Promise.resolve();
|
||
});
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_download"));
|
||
}
|
||
);
|
||
|
||
QUnit.test("download button is disabled when there is no data", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
serverData.models.partner.records = [];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" interval="month" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.ok(
|
||
target.querySelector(".o_pivot_download").disabled,
|
||
"download button should be disabled"
|
||
);
|
||
});
|
||
|
||
QUnit.test("correctly save measures and groupbys to favorite", async function (assert) {
|
||
assert.expect(3);
|
||
|
||
let expectedContext;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" interval="day" type="col"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContext);
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["date:day"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Fav1");
|
||
await saveFavorite(target);
|
||
|
||
// expand header on field customer
|
||
await click(target.querySelectorAll("thead .o_pivot_header_cell_closed")[1]);
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["date:day", "customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Fav2");
|
||
await saveFavorite(target);
|
||
|
||
// expand row on field product_id
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["date:day", "customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: ["product_id"],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Fav3");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test("correctly remove pivot_ keys from the context", async function (assert) {
|
||
assert.expect(5);
|
||
|
||
// in this test, we use "foo" as a measure
|
||
serverData.models.partner.fields.foo.store = true;
|
||
serverData.models.partner.fields.amount = {
|
||
string: "Amount",
|
||
type: "float",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
let expectedContext;
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContext);
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
// Unload the filter
|
||
await removeFacet(target); // remove previous favorite
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: ["product_id"],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "1");
|
||
await saveFavorite(target);
|
||
|
||
// Let's get rid of the rows groupBy
|
||
await click(target, "tbody .o_pivot_header_cell_opened");
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "2");
|
||
await saveFavorite(target);
|
||
|
||
// And now, get rid of both col and row groupby
|
||
//await click(target, "tbody .o_pivot_header_cell_opened"); //It was already removed
|
||
await click(target, "thead .o_pivot_header_cell_opened");
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: [],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "3");
|
||
await saveFavorite(target);
|
||
|
||
// Group row by product_id
|
||
await click(target, "tbody .o_pivot_header_cell_closed");
|
||
await click(target, ".dropdown-menu span:nth-child(5)");
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: [],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: ["product_id"],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "4");
|
||
await saveFavorite(target);
|
||
|
||
// Group column by customer
|
||
await click(target, "thead .o_pivot_header_cell_closed");
|
||
await click(target, ".dropdown-menu span:nth-child(2)");
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: ["product_id"],
|
||
};
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "5");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test("Apply two groupby, and remove facet", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<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>`,
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"First"
|
||
);
|
||
|
||
// Apply both groupbys
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Product");
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
|
||
await toggleMenuItem(target, "Bar");
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"Yes"
|
||
);
|
||
|
||
// remove filter
|
||
await removeFacet(target);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"Yes"
|
||
);
|
||
});
|
||
|
||
QUnit.test("Add a group by on the CP when a favorite already exists", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="groubybar" string="Bar" domain="[]" context="{'group_by': 'bar'}"/>
|
||
</search>`,
|
||
};
|
||
|
||
serverData.models.partner.filters = [
|
||
{
|
||
context: "{'pivot_row_groupby': ['date']}",
|
||
domain: "[]",
|
||
id: 7,
|
||
is_default: true,
|
||
name: "My favorite",
|
||
sort: "[]",
|
||
user_id: [2, "Mitchell Admin"],
|
||
},
|
||
];
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"April 2016"
|
||
);
|
||
|
||
// Apply BAR groupbys
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Bar");
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"No"
|
||
);
|
||
|
||
// remove groupBy
|
||
await toggleMenuItem(target, "Bar");
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"April 2016"
|
||
);
|
||
|
||
// remove all facets
|
||
await removeFacet(target);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"April 2016"
|
||
);
|
||
});
|
||
|
||
QUnit.skip("Removing or adding filter shouldn't modify the row group", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="bayou" string="Bayou" domain="[('foo','=', 12)]"/>
|
||
</search>`,
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
context: { search_default_bayou: 1, group_by: ["company_type"] },
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"Company"
|
||
);
|
||
|
||
// Let's get rid of the rows groupBy
|
||
await click(target, "tbody .o_pivot_header_cell_opened");
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"Total"
|
||
);
|
||
|
||
// Group row by product_id
|
||
await click(target, "tbody .o_pivot_header_cell_closed");
|
||
await click(target, ".dropdown-menu span:nth-child(5)");
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
|
||
// remove filter
|
||
await removeFacet(target);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"Adding a Favorite at anytime should modify the row/column groupby",
|
||
async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
<field name="date" interval="month" type="col" />
|
||
</pivot>`,
|
||
"partner,false,search": "<search/>",
|
||
};
|
||
serverData.models["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,
|
||
},
|
||
];
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"First"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("thead .o_pivot_header_cell_closed").textContent,
|
||
"April 2016"
|
||
);
|
||
|
||
// activate the unique existing favorite
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, 2);
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("thead .o_pivot_header_cell_closed").textContent,
|
||
"No"
|
||
);
|
||
|
||
// desactivate the unique existing favorite
|
||
await toggleMenuItem(target, 2);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("thead .o_pivot_header_cell_closed").textContent,
|
||
"No"
|
||
);
|
||
|
||
// Let's get rid of the rows and columns groupBy
|
||
await click(target, "tbody .o_pivot_header_cell_opened");
|
||
await click(target, "thead .o_pivot_header_cell_opened");
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"Total"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("thead .o_pivot_header_cell_closed").textContent,
|
||
"Total"
|
||
);
|
||
|
||
// activate AGAIN the unique existing favorite
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, 2);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").textContent,
|
||
"xphone"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("thead .o_pivot_header_cell_closed").textContent,
|
||
"No"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("Unload Filter, reset display, load another filter", async function (assert) {
|
||
assert.expect(18);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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
|
||
assert.strictEqual(
|
||
$(target).find("thead .o_pivot_header_cell_opened").length,
|
||
1,
|
||
"The column should be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("First")').length,
|
||
1,
|
||
'There should be a column "First"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("Second")').length,
|
||
1,
|
||
'There should be a column "Second"'
|
||
);
|
||
|
||
// Check Rows
|
||
assert.strictEqual(
|
||
$(target).find("tbody .o_pivot_header_cell_opened").length,
|
||
1,
|
||
"The row should be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xphone")').length,
|
||
1,
|
||
'There should be a row "xphone"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xpad")').length,
|
||
1,
|
||
'There should be a row "xpad"'
|
||
);
|
||
|
||
// Equivalent to unload the filter
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My fake favorite");
|
||
// collapse all headers
|
||
await click(target, ".o_pivot_header_cell_opened:first-child");
|
||
await click(target, ".o_pivot_header_cell_opened");
|
||
|
||
// Check Columns
|
||
assert.strictEqual(
|
||
$(target).find("thead .o_pivot_header_cell_closed").length,
|
||
1,
|
||
"The column should not be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("First")').length,
|
||
0,
|
||
'There should not be a column "First"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("Second")').length,
|
||
0,
|
||
'There should not be a column "Second"'
|
||
);
|
||
|
||
// Check Rows
|
||
assert.strictEqual(
|
||
$(target).find("tbody .o_pivot_header_cell_closed").length,
|
||
1,
|
||
"The row should not be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xphone")').length,
|
||
0,
|
||
'There should not be a row "xphone"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xpad")').length,
|
||
0,
|
||
'There should not be a row "xpad"'
|
||
);
|
||
|
||
// Equivalent to load another filter
|
||
await removeFacet(target); // remove previously saved favorite
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My fake favorite 2");
|
||
|
||
// Check Columns
|
||
assert.strictEqual(
|
||
$(target).find("thead .o_pivot_header_cell_opened").length,
|
||
1,
|
||
"The column should be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("First")').length,
|
||
1,
|
||
'There should be a column "First"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('thead tr:contains("Second")').length,
|
||
1,
|
||
'There should be a column "Second"'
|
||
);
|
||
|
||
// Check Rows
|
||
assert.strictEqual(
|
||
$(target).find("tbody .o_pivot_header_cell_opened").length,
|
||
1,
|
||
"The row should be grouped"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xphone")').length,
|
||
1,
|
||
'There should be a row "xphone"'
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("xpad")').length,
|
||
1,
|
||
'There should be a row "xpad"'
|
||
);
|
||
});
|
||
|
||
QUnit.test("Reload, group by columns, reload", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
let expectedContext;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContext);
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
// Set a column groupby
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-item")[1]);
|
||
|
||
// Set a domain
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "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(target);
|
||
await editFavoriteName(target, "My favorite 1");
|
||
await saveFavorite(target);
|
||
|
||
// Set a column groupby
|
||
await removeFacet(target); // remove previously saved favorite
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
|
||
|
||
// Set a domain
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter 2");
|
||
|
||
expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer", "product_id"],
|
||
pivot_measures: ["__count"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "My favorite 2");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test("folded groups remain folded at reload", async function (assert) {
|
||
assert.expect(5);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// expand a col group
|
||
await click(target.querySelectorAll("thead .o_pivot_header_cell_closed")[1]);
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
|
||
|
||
values = ["29", "2", "1", "32", "12", "12", "17", "2", "1", "20"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// expand a row group
|
||
await click(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
|
||
await click(target.querySelectorAll("tbody .dropdown-menu .dropdown-item")[3]);
|
||
|
||
values = ["29", "2", "1", "32", "12", "12", "17", "2", "1", "20", "17", "2", "1", "20"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// reload (should keep folded groups folded as col/row groupbys didn't change)
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Dummy Filter");
|
||
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
await click(target.querySelector(".o_pivot_expand_button"));
|
||
|
||
// 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",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
});
|
||
|
||
QUnit.test("Empty results keep groupbys", async function (assert) {
|
||
assert.expect(6);
|
||
|
||
const expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_measures: ["__count"],
|
||
pivot_row_groupby: [],
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContext);
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
// Set a column groupby
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[1]);
|
||
|
||
assert.containsOnce(target, "table");
|
||
|
||
// Set a domain for empty results
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter 1");
|
||
assert.containsNone(target, "table");
|
||
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "My favorite 1");
|
||
await saveFavorite(target);
|
||
|
||
// Set a domain for not empty results
|
||
await removeFacet(target); // remove previously saved favorite
|
||
assert.containsOnce(target, "table");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter 2");
|
||
assert.containsOnce(target, "table");
|
||
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "My favorite 2");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test("correctly uses pivot_ keys from the context", async function (assert) {
|
||
assert.expect(7);
|
||
|
||
// in this test, we use "foo" as a measure
|
||
serverData.models.partner.fields.foo.store = true;
|
||
serverData.models.partner.fields.amount = {
|
||
string: "Amount",
|
||
type: "float",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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"],
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_opened",
|
||
"column: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(First)",
|
||
"column: should display one closed header with 'First'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(Second)",
|
||
"column: should display one closed header with 'Second'"
|
||
);
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_opened",
|
||
"row: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xphone)",
|
||
"row: should display one closed header with 'xphone'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xpad)",
|
||
"row: should display one closed header with 'xpad'"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody tr").querySelectorAll("td")[2].innerText,
|
||
"32",
|
||
"selected measure should be foo, with total 32"
|
||
);
|
||
});
|
||
|
||
QUnit.test("clear table cells data after closeGroup", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot/>",
|
||
groupBy: ["product_id"],
|
||
});
|
||
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await mouseEnter(target.querySelector("thead .dropdown-menu .dropdown-toggle"));
|
||
await click(
|
||
target.querySelectorAll("thead .dropdown-menu .dropdown-menu .dropdown-item")[3]
|
||
);
|
||
|
||
// close and reopen row groupings after changing value
|
||
serverData.models.partner.records.find((r) => r.product_id === 37).date = "2016-10-27";
|
||
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_opened"));
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
|
||
assert.strictEqual(target.querySelectorAll(".o_pivot_cell_value")[4].innerText, ""); // xphone December 2016
|
||
|
||
// invert axis, and reopen column groupings
|
||
await click(target.querySelector(".o_pivot_buttons .o_pivot_flip_button"));
|
||
await click(target.querySelector("thead .o_pivot_header_cell_opened"));
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[4]);
|
||
assert.strictEqual(target.querySelectorAll(".o_pivot_cell_value")[3].innerText, ""); // December 2016 xphone
|
||
});
|
||
|
||
QUnit.test("correctly group data after flip (1)", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
serverData.views = {
|
||
"partner,false,pivot": "<pivot/>",
|
||
"partner,false,search": `<search><filter name="bayou" string="Bayou" domain="[(1,'=',1)]"/></search>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
"partner,false,form": '<form><field name="foo"/></form>',
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
context: { group_by: ["product_id"] },
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total", "xphone", "xpad"]
|
||
);
|
||
|
||
// flip axis
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total"]
|
||
);
|
||
|
||
// select filter "Bayou" in control panel
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Bayou");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total", "xphone", "xpad"]
|
||
);
|
||
|
||
// close row header "Total"
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_opened"));
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total"]
|
||
);
|
||
});
|
||
|
||
QUnit.test("correctly group data after flip (2)", async function (assert) {
|
||
assert.expect(5);
|
||
|
||
serverData.views = {
|
||
"partner,false,pivot": "<pivot/>",
|
||
"partner,false,search": `<search><filter name="bayou" string="Bayou" domain="[(1,'=',1)]"/></search>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
"partner,false,form": '<form><field name="foo"/></form>',
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
context: { group_by: ["product_id"] },
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total", "xphone", "xpad"]
|
||
);
|
||
|
||
// select filter "Bayou" in control panel
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Bayou");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total", "xphone", "xpad"]
|
||
);
|
||
|
||
// flip axis
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total"]
|
||
);
|
||
|
||
// unselect filter "Bayou" in control panel
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Bayou");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total", "xphone", "xpad"]
|
||
);
|
||
|
||
// close row header "Total"
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_opened"));
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((e) => e.innerText),
|
||
["Total"]
|
||
);
|
||
});
|
||
|
||
QUnit.test("correctly uses pivot_ keys from the context (at reload)", async function (assert) {
|
||
assert.expect(8);
|
||
|
||
// in this test, we use "foo" as a measure
|
||
serverData.models.partner.fields.foo.store = true;
|
||
serverData.models.partner.fields.amount = {
|
||
string: "Amount",
|
||
type: "float",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody tr").querySelectorAll("td.o_pivot_cell_value")[4].innerText,
|
||
"0.00",
|
||
"the active measure should be amount"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My fake favorite");
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_opened",
|
||
"column: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(First)",
|
||
"column: should display one closed header with 'First'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(Second)",
|
||
"column: should display one closed header with 'Second'"
|
||
);
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_opened",
|
||
"row: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xphone)",
|
||
"row: should display one closed header with 'xphone'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xpad)",
|
||
"row: should display one closed header with 'xpad'"
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody tr").querySelectorAll("td")[2].innerText,
|
||
"32",
|
||
"selected measure should be foo, with total 32"
|
||
);
|
||
});
|
||
|
||
QUnit.test("correctly use group_by key from the context", async function (assert) {
|
||
assert.expect(7);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="customer" type="col" />
|
||
<field name="foo" type="measure" />
|
||
</pivot>`,
|
||
groupBy: ["product_id"],
|
||
});
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_opened",
|
||
"column: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(First)",
|
||
'column: should display one closed header with "First"'
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(Second)",
|
||
'column: should display one closed header with "Second"'
|
||
);
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_opened",
|
||
"row: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xphone)",
|
||
'row: should display one closed header with "xphone"'
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xpad)",
|
||
'row: should display one closed header with "xpad"'
|
||
);
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("tbody tr").querySelectorAll("td")[2].innerText,
|
||
"32",
|
||
"selected measure should be foo, with total 32"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"correctly uses pivot_row_groupby key with default groupBy from the context",
|
||
async function (assert) {
|
||
assert.expect(6);
|
||
|
||
serverData.models.partner.fields.amount = {
|
||
string: "Amount",
|
||
type: "float",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="customer" type="col"/>
|
||
<field name="date" interval="day" type="row"/>
|
||
</pivot>`,
|
||
groupBy: ["customer"],
|
||
context: {
|
||
pivot_row_groupby: ["product_id"],
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_opened",
|
||
"column: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(First)",
|
||
"column: should display one closed header with 'First'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .o_pivot_header_cell_closed:contains(Second)",
|
||
"column: should display one closed header with 'Second'"
|
||
);
|
||
|
||
// With pivot_row_groupby, groupBy customer should replace and eventually display product_id
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_opened",
|
||
"row: should have one opened header"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xphone)",
|
||
"row: should display one closed header with 'xphone'"
|
||
);
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed:contains(xpad)",
|
||
"row: should display one closed header with 'xpad'"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot still handles __count__ measure", async function (assert) {
|
||
// for retro-compatibility reasons, the pivot view still handles
|
||
// '__count__' measure.
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot></pivot>",
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
assert.deepEqual(
|
||
args.kwargs.fields,
|
||
["__count"],
|
||
"should make a read_group with field __count"
|
||
);
|
||
}
|
||
},
|
||
context: {
|
||
pivot_measures: ["__count__"],
|
||
},
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-item");
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_buttons .dropdown-item").innerText,
|
||
"Count"
|
||
);
|
||
assert.hasClass(target.querySelector(".o_pivot_buttons .dropdown-item"), "selected");
|
||
});
|
||
|
||
QUnit.test("not use a many2one as a measure by default", async function (assert) {
|
||
assert.expect(3);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-item");
|
||
assert.strictEqual(
|
||
target.querySelector(".o_pivot_buttons .dropdown-item").innerText,
|
||
"Count"
|
||
);
|
||
assert.hasClass(target.querySelector(".o_pivot_buttons .dropdown-item"), "selected");
|
||
});
|
||
|
||
QUnit.test("pivot view with many2one field as a measure", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("table tbody tr").innerText.replace(/\s/g, ""),
|
||
"Total1122",
|
||
"should display product_id count as measure"
|
||
);
|
||
});
|
||
|
||
QUnit.test("pivot view with reference field as a measure", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="ref" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.strictEqual(
|
||
target.querySelector("table tbody tr").innerText.replace(/\s/g, ""),
|
||
"Total1124",
|
||
"should display ref distinct count as measure"
|
||
);
|
||
});
|
||
|
||
QUnit.test("m2o as measure, drilling down into data", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
// click on date by month
|
||
await mouseEnter(target.querySelector("tbody .dropdown-menu .dropdown-toggle"));
|
||
await click(
|
||
target.querySelectorAll("tbody .dropdown-menu .dropdown-menu .dropdown-item")[3]
|
||
);
|
||
|
||
assert.strictEqual(
|
||
[...target.querySelectorAll(".o_pivot_cell_value")].map((c) => c.innerText).join(""),
|
||
"2112",
|
||
"should have loaded the proper data"
|
||
);
|
||
});
|
||
|
||
QUnit.test("Row and column groupbys plus a domain", async function (assert) {
|
||
assert.expect(3);
|
||
|
||
const expectedContext = {
|
||
group_by: [],
|
||
pivot_column_groupby: ["customer"],
|
||
pivot_measures: ["foo"],
|
||
pivot_row_groupby: ["product_id"],
|
||
};
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="some_filter" string="Some Filter" domain="[('product_id', '=', 41)]"/>
|
||
</search>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContext);
|
||
return true;
|
||
}
|
||
},
|
||
});
|
||
|
||
// Set a column groupby
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-item")[1]);
|
||
|
||
// Set a Row groupby
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("tbody .dropdown-item")[4]);
|
||
|
||
// Add a filter
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Some Filter");
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"tbody .o_pivot_header_cell_closed",
|
||
"There should be only one product line because of the domain"
|
||
);
|
||
assert.strictEqual(
|
||
target.querySelector("tbody .o_pivot_header_cell_closed").innerText,
|
||
"xpad",
|
||
"The product should be the right one"
|
||
);
|
||
|
||
// Save current search to favorite
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "My favorite");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test(
|
||
"parallel data loading should discard all but the last one",
|
||
async function (assert) {
|
||
assert.expect(6);
|
||
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_cell_value", "should have 1 cell initially");
|
||
assert.containsOnce(target, "tbody tr", "should have 1 row initially");
|
||
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Product");
|
||
await toggleMenuItem(target, "Customer");
|
||
|
||
assert.containsOnce(target, ".o_pivot_cell_value", "should still have 1 cell");
|
||
assert.containsOnce(target, "tbody tr", "should still have 1 row");
|
||
|
||
await def.resolve();
|
||
await nextTick();
|
||
|
||
assert.containsN(target, ".o_pivot_cell_value", 6, "should have 6 cells");
|
||
assert.containsN(target, "tbody tr", 6, "should have 6 rows");
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot measures should be alphabetically sorted", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
// It's important to compare capitalized and lowercased words
|
||
// to be sure the sorting is effective with both of them
|
||
serverData.models.partner.fields.bouh = {
|
||
string: "bouh",
|
||
type: "integer",
|
||
group_operator: "sum",
|
||
};
|
||
serverData.models.partner.fields.modd = {
|
||
string: "modd",
|
||
type: "integer",
|
||
group_operator: "sum",
|
||
};
|
||
serverData.models.partner.fields.zip = {
|
||
string: "Zip",
|
||
type: "integer",
|
||
group_operator: "sum",
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="zip" type="measure"/>
|
||
<field name="foo" type="measure"/>
|
||
<field name="bouh" type="measure"/>
|
||
<field name="modd" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
assert.strictEqual(
|
||
[...target.querySelectorAll(".o_pivot_buttons .dropdown-item")]
|
||
.map((i) => i.innerText)
|
||
.join(""),
|
||
"bouhFoomoddZipCount"
|
||
);
|
||
});
|
||
|
||
QUnit.test("pivot view should use default order for auto sorting", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot default_order="foo asc">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.hasClass(
|
||
target.querySelector("thead th.o_pivot_measure_row"),
|
||
"o_pivot_sort_order_asc"
|
||
);
|
||
});
|
||
|
||
QUnit.test("pivot view can be flipped", async function (assert) {
|
||
assert.expect(5);
|
||
|
||
var rpcCount = 0;
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
mockRPC() {
|
||
rpcCount++;
|
||
},
|
||
});
|
||
|
||
assert.containsN(
|
||
target,
|
||
"tbody tr",
|
||
3,
|
||
"should have 3 rows: 1 for the open header, and 2 for data"
|
||
);
|
||
let values = ["4", "1", "3"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
rpcCount = 0;
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
|
||
assert.strictEqual(rpcCount, 0, "should not have done any rpc");
|
||
assert.containsOnce(target, "tbody tr", "should have 1 rows: 1 for the main header");
|
||
|
||
values = ["1", "3", "4"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
});
|
||
|
||
QUnit.test("rendering of pivot view with comparison", async function (assert) {
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='last_year'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: 1 },
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, {
|
||
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;
|
||
}
|
||
},
|
||
});
|
||
|
||
// with no data
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
assert.containsOnce(target, "p.o_view_nocontent_empty_folder");
|
||
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "December");
|
||
await toggleMenuItemOption(target, "Date", "2016");
|
||
await toggleMenuItemOption(target, "Date", "2015");
|
||
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot thead tr:last th",
|
||
9,
|
||
"last header row should contains 9 cells (3*[December 2016, November 2016, Variation]"
|
||
);
|
||
let values = ["19", "0", "-100%", "0", "13", "100%", "19", "13", "-31.58%"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// with data, with row groupby
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("tbody .dropdown-menu .dropdown-item")[4]);
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
await click(target.querySelector(".o_pivot_buttons button.dropdown-toggle"));
|
||
|
||
await click(target.querySelectorAll(".o_pivot_buttons .dropdown-menu .dropdown-item")[0]);
|
||
await click(target.querySelectorAll(".o_pivot_buttons .dropdown-menu .dropdown-item")[1]);
|
||
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%"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
await click(target.querySelector("thead .o_pivot_header_cell_opened"));
|
||
values = ["2", "2", "0%", "2", "1", "-50%", "0", "1", "100%"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Fav");
|
||
await saveFavorite(target);
|
||
});
|
||
|
||
QUnit.test("export data in excel with comparison", async function (assert) {
|
||
assert.expect(12);
|
||
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
mockDownload(({ url, data }) => {
|
||
data = JSON.parse(data.data);
|
||
for (const l of data.col_group_headers) {
|
||
const titles = l.map((o) => o.title);
|
||
assert.step(JSON.stringify(titles));
|
||
}
|
||
const measures = data.measure_headers.map((o) => o.title);
|
||
assert.step(JSON.stringify(measures));
|
||
const origins = data.origin_headers.map((o) => o.title);
|
||
assert.step(JSON.stringify(origins));
|
||
assert.step(String(data.measure_count));
|
||
assert.step(String(data.origin_count));
|
||
const valuesLength = data.rows.map((o) => o.values.length);
|
||
assert.step(JSON.stringify(valuesLength));
|
||
assert.strictEqual(
|
||
url,
|
||
"/web/pivot/export_xlsx",
|
||
"should call get_file with correct parameters"
|
||
);
|
||
return Promise.resolve();
|
||
});
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='antepenultimate_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: 1 },
|
||
});
|
||
|
||
// open comparison menu
|
||
await toggleSearchBarMenu(target);
|
||
// compare October 2016 to September 2016
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
// With the data above, the time ranges contain no record.
|
||
assert.containsOnce(target, "p.o_view_nocontent_empty_folder", "there should be no data");
|
||
// export data should be impossible since the pivot buttons
|
||
// are deactivated (exception: the 'Measures' button).
|
||
assert.ok(target.querySelector(".o_pivot_buttons button.o_pivot_download").disabled);
|
||
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "December");
|
||
await toggleMenuItemOption(target, "Date", "October");
|
||
assert.notOk(target.querySelector(".o_pivot_buttons button.o_pivot_download").disabled);
|
||
|
||
// With the data above, the time ranges contain some records.
|
||
// export data. Should execute 'get_file'
|
||
await click(target.querySelector(".o_pivot_buttons button.o_pivot_download"));
|
||
|
||
assert.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]",
|
||
]);
|
||
});
|
||
|
||
QUnit.test("rendering pivot view with comparison and count measure", async function (assert) {
|
||
assert.expect(2);
|
||
|
||
let mockMock = false;
|
||
let nbReadGroup = 0;
|
||
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-12-22";
|
||
serverData.models.partner.records[3].date = "2016-12-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: '<pivot><field name="customer" type="row"/></pivot>',
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name="date_filter" date="date" domain="[]" default_period='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: 1 },
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group" && 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 Promise.resolve([{}]);
|
||
}
|
||
}
|
||
},
|
||
});
|
||
|
||
mockMock = true;
|
||
|
||
// compare December 2016 to November 2016
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
const values = ["0", "4", "100%", "0", "2", "100%", "0", "2", "100%"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
assert.containsN(
|
||
target,
|
||
".o_pivot_header_cell_closed",
|
||
3,
|
||
"there should be exactly three closed header ('Total','First', 'Second')"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"can sort a pivot view with comparison by clicking on header",
|
||
async function (assert) {
|
||
assert.expect(6);
|
||
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: 1 },
|
||
});
|
||
|
||
// compare December 2016 to November 2016
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// click on 'Foo' in column Total/Company (should sort by the period of interest, ASC)
|
||
await click(target.querySelector(".o_pivot_measure_row"));
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// click again on 'Foo' in column Total/Company (should sort by the period of interest, DESC)
|
||
await click(target.querySelector(".o_pivot_measure_row"));
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// click on 'This Month' in column Total/Individual/Foo
|
||
await click(target.querySelectorAll(".o_pivot_origin_row")[3]);
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// click on 'Previous Period' in column Total/Individual/Foo
|
||
await click(target.querySelectorAll(".o_pivot_origin_row")[4]);
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// click on 'Variation' in column Total/Foo
|
||
await click(target.querySelectorAll(".o_pivot_origin_row")[8]);
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
}
|
||
);
|
||
|
||
QUnit.test("Click on the measure list but not on a menu item", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
// have at least a measure to have a separator in the Measures dropdown:
|
||
//
|
||
// Foo
|
||
// -----
|
||
// Count
|
||
arch: `<pivot><field name="foo" type="measure"/></pivot>`,
|
||
});
|
||
|
||
assert.containsNone(target, ".o_pivot_buttons .dropdown-menu");
|
||
|
||
// open the "Measures" menu
|
||
await click(target.querySelector(".o_pivot_buttons .dropdown-toggle"));
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-menu");
|
||
|
||
// click on the divider in the "Measures" menu does not crash
|
||
await click(target.querySelector(".o_pivot_buttons .dropdown-menu .dropdown-divider"));
|
||
// the menu should still be open
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-menu");
|
||
|
||
// click on the measure list but not on a menu item or the separator
|
||
await click(target.querySelector(".o_pivot_buttons .dropdown-menu"));
|
||
// the menu should still be open
|
||
assert.containsOnce(target, ".o_pivot_buttons .dropdown-menu");
|
||
});
|
||
|
||
QUnit.test(
|
||
"Navigation list view for a group and back with breadcrumbs",
|
||
async function (assert) {
|
||
assert.expect(16);
|
||
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="bayou" string="Bayou" domain="[('foo','=', 12)]"/>
|
||
</search>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
"partner,false,form": '<form><field name="foo"/></form>',
|
||
};
|
||
|
||
let readGroupCount = 0;
|
||
const mockRPC = (route, args) => {
|
||
if (args.method === "read_group") {
|
||
assert.step("read_group");
|
||
const domain = args.kwargs.domain;
|
||
if ([0, 1].indexOf(readGroupCount) !== -1) {
|
||
assert.deepEqual(domain, [], "domain empty");
|
||
} else if ([2, 3, 4, 5].indexOf(readGroupCount) !== -1) {
|
||
assert.deepEqual(
|
||
domain,
|
||
[["foo", "=", 12]],
|
||
"domain conserved when back with breadcrumbs"
|
||
);
|
||
}
|
||
readGroupCount++;
|
||
}
|
||
if (args.method === "web_search_read") {
|
||
assert.step("web_search_read");
|
||
const domain = args.kwargs.domain;
|
||
assert.deepEqual(
|
||
domain,
|
||
["&", ["customer", "=", 1], ["foo", "=", 12]],
|
||
"list domain is correct"
|
||
);
|
||
}
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData, mockRPC });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [[false, "pivot"]],
|
||
});
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, 0);
|
||
await nextTick();
|
||
|
||
await click(target.querySelectorAll(".o_pivot_cell_value")[1]);
|
||
|
||
assert.containsOnce(target, ".o_list_view");
|
||
|
||
await click(target.querySelector(".o_control_panel ol.breadcrumb li.breadcrumb-item"));
|
||
|
||
assert.verifySteps([
|
||
"read_group",
|
||
"read_group",
|
||
"read_group",
|
||
"read_group",
|
||
"web_search_read",
|
||
"read_group",
|
||
"read_group",
|
||
]);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"Cell values are kept when flippin a pivot view in comparison mode",
|
||
async function (assert) {
|
||
assert.expect(2);
|
||
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='this_month'/>
|
||
</search>`,
|
||
context: { search_default_date_filter: 1 },
|
||
});
|
||
|
||
// compare December 2016 to November 2016
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
|
||
// flip table
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
|
||
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%",
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
}
|
||
);
|
||
|
||
QUnit.test("Flip then compare, table col groupbys are kept", async function (assert) {
|
||
assert.expect(6);
|
||
|
||
serverData.models.partner.records[0].date = "2016-12-15";
|
||
serverData.models.partner.records[1].date = "2016-12-17";
|
||
serverData.models.partner.records[2].date = "2016-11-22";
|
||
serverData.models.partner.records[3].date = "2016-11-03";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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='this_month'/>
|
||
</search>`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "", "Company", "individual", "Foo", "Foo", "Foo"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "2016-11-03", "2016-11-22", "2016-12-15", "2016-12-17"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
// flip
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
[
|
||
"",
|
||
"Total",
|
||
"",
|
||
"2016-11-03",
|
||
"2016-11-22",
|
||
"2016-12-15",
|
||
"2016-12-17",
|
||
"Foo",
|
||
"Foo",
|
||
"Foo",
|
||
"Foo",
|
||
"Foo",
|
||
],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Company", "individual"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
// Filter on December 2016
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "December");
|
||
|
||
// compare December 2016 to November 2016
|
||
await toggleMenuItem(target, "Date: Previous period");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
[
|
||
"",
|
||
"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",
|
||
],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Company", "individual"],
|
||
"The row headers should be as expected"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"correctly compute group domain when a date field has false value",
|
||
async function (assert) {
|
||
assert.expect(1);
|
||
|
||
serverData.models.partner.records.forEach((r) => (r.date = false));
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
|
||
const fakeActionService = {
|
||
start() {
|
||
return {
|
||
doAction(action) {
|
||
assert.deepEqual(action.domain, [["date", "=", false]]);
|
||
return Promise.resolve(true);
|
||
},
|
||
};
|
||
},
|
||
};
|
||
serviceRegistry.add("action", fakeActionService, { force: true });
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot o_enable_linking="1">
|
||
<field name="date" interval="day" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
await click(target.querySelectorAll(".o_value")[1]);
|
||
}
|
||
);
|
||
QUnit.test(
|
||
"Does not identify 'false' with false as keys when creating group trees",
|
||
async function (assert) {
|
||
assert.expect(2);
|
||
|
||
serverData.models.partner.fields.favorite_animal = {
|
||
string: "Favorite animal",
|
||
type: "char",
|
||
store: true,
|
||
};
|
||
serverData.models.partner.records[0].favorite_animal = "Dog";
|
||
serverData.models.partner.records[1].favorite_animal = "false";
|
||
serverData.models.partner.records[2].favorite_animal = "None";
|
||
|
||
patchDate(2016, 11, 20, 1, 0, 0);
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot o_enable_linking="1">
|
||
<field name="favorite_animal" type="row"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "Count"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Dog", "None", "false", "None"],
|
||
"The row headers should be as expected"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"group bys added via control panel and expand Header do not stack",
|
||
async function (assert) {
|
||
assert.expect(8);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "Foo"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
// open group by menu and add new groupby
|
||
await toggleSearchBarMenu(target);
|
||
await selectGroup(target, "company_type");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "Foo"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Company", "individual"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
// Set a Row groupby
|
||
await click(target, "tbody tr:nth-child(2) .o_pivot_header_cell_closed");
|
||
await click(target, "tbody .dropdown-menu .o_menu_item:nth-child(5)");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "Foo"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Company", "xphone", "xpad", "individual"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
// open groupby menu generator and add a new groupby
|
||
await toggleSearchBarMenu(target);
|
||
await selectGroup(target, "bar");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((th) => th.innerText),
|
||
["", "Total", "Foo"],
|
||
"The col headers should be as expected"
|
||
);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((th) => th.innerText),
|
||
["Total", "Company", "Yes", "individual", "No", "Yes"],
|
||
"The row headers should be as expected"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("display only one dropdown menu", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
// add a col groupby on Product
|
||
await click(target.querySelector("thead th.o_pivot_header_cell_closed"));
|
||
await click(target.querySelectorAll("thead .dropdown-menu .dropdown-item")[5]);
|
||
|
||
// Click on the two header dropdown togglers
|
||
await click(target.querySelectorAll("thead th.o_pivot_header_cell_closed")[0]);
|
||
await click(target.querySelectorAll("thead th.o_pivot_header_cell_closed")[1]);
|
||
|
||
assert.containsOnce(
|
||
target,
|
||
"thead .dropdown-menu",
|
||
"Only one dropdown should be displayed at a time"
|
||
);
|
||
});
|
||
|
||
QUnit.test("Server order is kept by default", async function (assert) {
|
||
assert.expect(1);
|
||
|
||
let isSecondReadGroup = false;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
if (isSecondReadGroup) {
|
||
return Promise.resolve([
|
||
{
|
||
customer: [2, "Second"],
|
||
foo: 18,
|
||
__count: 2,
|
||
__domain: [["customer", "=", 2]],
|
||
},
|
||
{
|
||
customer: [1, "First"],
|
||
foo: 14,
|
||
__count: 2,
|
||
__domain: [["customer", "=", 1]],
|
||
},
|
||
]);
|
||
}
|
||
isSecondReadGroup = true;
|
||
}
|
||
},
|
||
});
|
||
|
||
const values = [
|
||
"32", // Total Value
|
||
"18", // Second
|
||
"14", // First
|
||
];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
});
|
||
|
||
QUnit.test("pivot rendering with boolean field", async function (assert) {
|
||
assert.expect(4);
|
||
|
||
serverData.models.partner.fields.bar = {
|
||
string: "bar",
|
||
type: "boolean",
|
||
store: true,
|
||
searchable: true,
|
||
group_operator: "bool_or",
|
||
};
|
||
serverData.models.partner.records = [
|
||
{ id: 1, bar: true, date: "2019-12-14" },
|
||
{ id: 2, bar: false, date: "2019-05-14" },
|
||
];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="date" type="row" interval="day"/>
|
||
<field name="bar" type="col"/>
|
||
<field name="bar" string="SLA status Failed" type="measure"/>
|
||
</pivot>`,
|
||
});
|
||
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("2019-12-14")').length,
|
||
1,
|
||
"There should be a first column"
|
||
);
|
||
assert.ok(
|
||
$(target).find('tbody tr:contains("2019-12-14") [type="checkbox"]').is(":checked"),
|
||
"first column contains checkbox and value should be ticked"
|
||
);
|
||
assert.strictEqual(
|
||
$(target).find('tbody tr:contains("2019-05-14")').length,
|
||
1,
|
||
"There should be a second column"
|
||
);
|
||
assert.notOk(
|
||
$(target).find('tbody tr:contains("2019-05-14") [type="checkbox"]').is(":checked"),
|
||
"second column should have checkbox that is not checked by default"
|
||
);
|
||
});
|
||
|
||
QUnit.test("empty pivot view with action helper", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`,
|
||
};
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
context: { search_default_small_than_0: true },
|
||
noContentHelp: markup('<p class="abc">click to add a foo</p>'),
|
||
config: {
|
||
views: [[false, "search"]],
|
||
},
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_view_nocontent .abc");
|
||
assert.containsNone(target, "table");
|
||
|
||
await removeFacet(target);
|
||
|
||
assert.containsNone(target, ".o_view_nocontent .abc");
|
||
assert.containsOnce(target, "table");
|
||
});
|
||
|
||
QUnit.test("empty pivot view with sample data", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot sample="1">
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`,
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
context: { search_default_small_than_0: true },
|
||
noContentHelp: markup('<p class="abc">click to add a foo</p>'),
|
||
config: {
|
||
views: [[false, "search"]],
|
||
},
|
||
});
|
||
|
||
assert.hasClass(target.querySelector(".o_pivot_view .o_content"), "o_view_sample_data");
|
||
assert.containsOnce(target, ".o_view_nocontent .abc");
|
||
|
||
await removeFacet(target);
|
||
|
||
assert.doesNotHaveClass(
|
||
target.querySelector(".o_pivot_view .o_content"),
|
||
"o_view_sample_data"
|
||
);
|
||
assert.containsNone(target, ".o_view_nocontent .abc");
|
||
assert.containsOnce(target, "table");
|
||
});
|
||
|
||
QUnit.test("non empty pivot view with sample data", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot sample="1">
|
||
<field name="product_id" type="measure"/>
|
||
<field name="date" interval="month" type="col"/>
|
||
</pivot>`,
|
||
"partner,false,search": `
|
||
<search>
|
||
<filter name="small_than_0" string="Small Than 0" domain="[('id', '<', 0)]"/>
|
||
</search>`,
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
noContentHelp: markup('<p class="abc">click to add a foo</p>'),
|
||
config: {
|
||
views: [[false, "search"]],
|
||
},
|
||
});
|
||
|
||
assert.doesNotHaveClass(target, "o_view_sample_data");
|
||
assert.containsNone(target, ".o_view_nocontent .abc");
|
||
assert.containsOnce(target, "table");
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Small Than 0");
|
||
|
||
assert.doesNotHaveClass(target, "o_view_sample_data");
|
||
assert.containsOnce(target, ".o_view_nocontent .abc");
|
||
assert.containsNone(target, "table");
|
||
});
|
||
|
||
QUnit.test("pivot is reloaded when leaving and coming back", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `<search/>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
};
|
||
|
||
const mockRPC = (route, args) => {
|
||
assert.step(args.method || route);
|
||
};
|
||
const webClient = await createWebClient({ serverData, mockRPC });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [
|
||
[false, "pivot"],
|
||
[false, "list"],
|
||
],
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["4", "2", "2"].join(","));
|
||
|
||
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read_group", "read_group"]);
|
||
|
||
// switch to list view
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_list"));
|
||
|
||
assert.containsOnce(target, ".o_list_view");
|
||
assert.verifySteps(["web_search_read"]);
|
||
|
||
// switch back to pivot
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_pivot"));
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["4", "2", "2"].join(","));
|
||
|
||
assert.verifySteps(["read_group", "read_group"]);
|
||
});
|
||
|
||
QUnit.test("expanded groups are kept when leaving and coming back", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `<search/>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [
|
||
[false, "pivot"],
|
||
[false, "list"],
|
||
],
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["4", "2", "2"].join(","));
|
||
|
||
// drill down first row group (group by company_type)
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelector(".o_pivot .dropdown-menu .dropdown-item"));
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["4", "2", "1", "1", "2"].join(","));
|
||
|
||
// switch to list view
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_list"));
|
||
|
||
assert.containsOnce(target, ".o_list_view");
|
||
|
||
// switch back to pivot
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_pivot"));
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["4", "2", "1", "1", "2"].join(","));
|
||
});
|
||
|
||
QUnit.test("sorted rows are kept when leaving and coming back", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `<search/>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
};
|
||
|
||
const webClient = await createWebClient({ serverData });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [
|
||
[false, "pivot"],
|
||
[false, "list"],
|
||
],
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// sort the first group
|
||
await click(target.querySelector("th.o_pivot_measure_row"));
|
||
await click(target.querySelector("th.o_pivot_measure_row"));
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "20", "12"].join(","));
|
||
|
||
// switch to list view
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_list"));
|
||
|
||
assert.containsOnce(target, ".o_list_view");
|
||
|
||
// switch back to pivot
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_pivot"));
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "20", "12"].join(","));
|
||
});
|
||
|
||
QUnit.test("correctly handle concurrent reloads", async function (assert) {
|
||
serverData.views = {
|
||
"partner,false,pivot": `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
"partner,false,search": `<search/>`,
|
||
"partner,false,list": '<tree><field name="foo"/></tree>',
|
||
};
|
||
|
||
let def;
|
||
let readGroupCount = 0;
|
||
const mockRPC = (route, args) => {
|
||
if (args.method === "read_group" && def) {
|
||
readGroupCount++;
|
||
if (readGroupCount === 2) {
|
||
// slow down last read_group of first reload
|
||
return Promise.resolve(def);
|
||
}
|
||
}
|
||
};
|
||
const webClient = await createWebClient({ serverData, mockRPC });
|
||
|
||
await doAction(webClient, {
|
||
res_model: "partner",
|
||
type: "ir.actions.act_window",
|
||
views: [
|
||
[false, "pivot"],
|
||
[false, "list"], // s.t. there is a pivot view switcher
|
||
],
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_view");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// drill down first row group (group by company_type)
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelector(".o_pivot .dropdown-menu .dropdown-item"));
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "12", "20"].join(","));
|
||
|
||
// reload twice by clicking on pivot view switcher
|
||
def = makeDeferred();
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_pivot"));
|
||
await click(target.querySelector(".o_control_panel .o_switch_view.o_pivot"));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "12", "20"].join(","));
|
||
});
|
||
|
||
QUnit.test("consecutively toggle several measures", async function (assert) {
|
||
let def;
|
||
serverData.models.partner.fields.foo2 = {
|
||
...serverData.models.partner.fields.foo,
|
||
string: "Foo2",
|
||
store: true,
|
||
};
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Toggle several measures (the reload is blocked, so all measures should be toggled in once)
|
||
def = makeDeferred();
|
||
await toggleMenu(target, "Measures");
|
||
await toggleMenuItem(target, "Foo2"); // add foo2
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
await toggleMenuItem(target, "Foo"); // remove foo
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
await toggleMenuItem(target, "Count"); // add count
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["0", "4", "0", "1", "0", "3"].join(","));
|
||
});
|
||
|
||
QUnit.test("flip axis while loading a filter", async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
const values = ["2", "1", "29", "32", "12", "12", "2", "1", "17", "20"];
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
// Flip axis
|
||
await click(target.querySelector(".o_pivot_flip_button"));
|
||
assert.strictEqual(getCurrentValues(target), values.join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["20", "2", "1", "17"].join(","));
|
||
});
|
||
|
||
QUnit.test("sort rows while loading a filter", async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Sort rows (this operation should be ignored as it concerns the old
|
||
// table, which will be replaced soon)
|
||
await click(target.querySelector("th.o_pivot_measure_row"));
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["20", "20"].join(","));
|
||
});
|
||
|
||
QUnit.test("close a group while loading a filter", async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Close a group (this operation should be ignored as it concerns the old
|
||
// table, which will be replaced soon)
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_opened"));
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["20", "20"].join(","));
|
||
});
|
||
|
||
QUnit.test("add a groupby while loading a filter", async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Add a groupby (this operation should be ignored as it concerns the old
|
||
// table, which will be replaced soon)
|
||
await click(target.querySelector("thead .o_pivot_header_cell_closed"));
|
||
await click(target.querySelector("thead .dropdown-menu .dropdown-item"));
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["20", "20"].join(","));
|
||
});
|
||
|
||
QUnit.test("expand a group while loading a filter", async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
// Add a groupby, to have a group to expand afterwards
|
||
await click(target.querySelector("tbody .o_pivot_header_cell_closed"));
|
||
await click(target.querySelector("tbody .dropdown-menu .dropdown-item"));
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "12", "20"].join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
assert.strictEqual(getCurrentValues(target), ["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 click(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[1]);
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["20", "20"].join(","));
|
||
});
|
||
|
||
QUnit.test(
|
||
"concurrent reloads: add a filter, and directly toggle a measure",
|
||
async function (assert) {
|
||
let def;
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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>`,
|
||
mockRPC(route, args) {
|
||
if (args.method === "read_group") {
|
||
return Promise.resolve(def);
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Set a domain (this reload is delayed)
|
||
def = makeDeferred();
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My Filter");
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
// Toggle a measure
|
||
await toggleMenu(target, "Measures");
|
||
await toggleMenuItem(target, "Count");
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["32", "12", "20"].join(","));
|
||
|
||
def.resolve();
|
||
await nextTick();
|
||
|
||
assert.strictEqual(getCurrentValues(target), ["12", "1", "12", "1"].join(","));
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"if no measure is set in arch, 'Count' is used as measure initially",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot/>`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((e) => e.innerText),
|
||
["", "Total", "Count"]
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.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 function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="foo" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((e) => e.innerText),
|
||
["", "Total", "Foo"]
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"if (at least) one measure is set in arch and display_quantity is true, 'Count' is used as measure initially",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot display_quantity="1">
|
||
<field name="foo" type="measure"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("thead th")].map((e) => e.innerText),
|
||
["", "Total", "Count", "Foo"]
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("'Measures' menu when there is no measurable fields", async function (assert) {
|
||
serverData.models.partner = {
|
||
fields: {},
|
||
records: [{ id: 1, display_name: "The one" }],
|
||
};
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot/>`,
|
||
});
|
||
|
||
await toggleMenu(target, "Measures");
|
||
|
||
// "Count" is the only measure available
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll(".o_pivot_buttons .dropdown-menu .o_menu_item")].map(
|
||
(e) => e.innerText
|
||
),
|
||
["Count"]
|
||
);
|
||
// No separator should be displayed in the menu "Measures"
|
||
assert.containsNone(target, ".o_pivot_buttons .dropdown-menu div.dropdown-divider");
|
||
});
|
||
|
||
QUnit.test(
|
||
"comparison with two groupbys: rows from reference period should be displayed",
|
||
async function (assert) {
|
||
patchDate(2023, 2, 22, 1, 0, 0);
|
||
assert.expect(3);
|
||
|
||
serverData.models.partner.records = [
|
||
{ id: 1, date: "2021-10-10", product_id: 1, customer: 1 },
|
||
{ id: 2, date: "2020-10-10", product_id: 2, customer: 1 },
|
||
];
|
||
serverData.models.product.records = [
|
||
{ id: 1, display_name: "A" },
|
||
{ id: 2, display_name: "B" },
|
||
];
|
||
serverData.models.customer.records = [{ id: 1, display_name: "P" }];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot>
|
||
<field name="customer" type="row"/>
|
||
<field name="product_id" type="row"/>
|
||
</pivot>
|
||
`,
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name='date' date='date'/>
|
||
</search>
|
||
`,
|
||
});
|
||
|
||
// compare 2021 to 2020
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Date");
|
||
await toggleMenuItemOption(target, "Date", "2021");
|
||
await toggleMenuItem(target.querySelector(".o_comparison_menu"), 0);
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(0, 6).map((el) => el.innerText),
|
||
["", "Total", "Count", "2020", "2021", "Variation"],
|
||
"The col headers should be as expected"
|
||
);
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(6).map((el) => el.innerText),
|
||
["Total", "P", "B", "A"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
const values = ["1", "1", "0%", "1", "1", "0%", "1", "0", "-100%", "0", "1", "100%"];
|
||
assert.strictEqual(getCurrentValues(target), values.join());
|
||
}
|
||
);
|
||
|
||
QUnit.test("pivot_row_groupby should be also used after first load", async function (assert) {
|
||
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"],
|
||
},
|
||
];
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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"],
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, expectedContexts.shift());
|
||
return ids.shift();
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "No", "Yes"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await click(target.querySelector("tbody th")); // click on row header "Total"
|
||
await click(target.querySelector("tbody th")); // click on row header "Total"
|
||
await click(target.querySelector("tbody .o_group_by_menu .o_menu_item")); // select "Product"
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "xphone", "xpad"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Favorite");
|
||
await saveFavorite(target);
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "xphone", "xpad"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await removeFacet(target);
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "No", "Yes"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Favorite");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "xphone", "xpad"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await toggleMenuItem(target, "customer");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "xphone", "First", "xpad", "First", "Second"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await click(target.querySelector("tbody th")); // click on row header "Total"
|
||
await click(target.querySelector("tbody th")); // click on row header "Total"
|
||
await click(target.querySelectorAll("tbody .o_group_by_menu .o_menu_item")[1]); // select "Customer"
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "First", "Second"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Favorite 2");
|
||
await saveFavorite(target);
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "First", "Second"],
|
||
"The row headers should be as expected"
|
||
);
|
||
});
|
||
|
||
QUnit.test(
|
||
"pivot_row_groupby should be also used after first load (2)",
|
||
async function (assert) {
|
||
await makeView({
|
||
serverData,
|
||
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,
|
||
},
|
||
],
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "xphone", "xpad"],
|
||
"The row headers should be as expected"
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "Favorite");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(3).map((el) => el.innerText),
|
||
["Total", "First", "Second"],
|
||
"The row headers should be as expected"
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"specific pivot keys in action context must have less importance than in favorite context",
|
||
async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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,
|
||
},
|
||
],
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(1, 6).map((el) => el.innerText),
|
||
["Total", "", "No", "Yes", "Computed and not stored"]
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "My favorite 2");
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(1, 11).map((el) => el.innerText),
|
||
[
|
||
"Total",
|
||
"",
|
||
"xphone",
|
||
"xpad",
|
||
"Computed and not stored",
|
||
"Count",
|
||
"Computed and not stored",
|
||
"Count",
|
||
"Computed and not stored",
|
||
"Count",
|
||
]
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test(
|
||
"favorite pivot_measures should be used even if found also in global context",
|
||
async function (assert) {
|
||
serverData.models.partner.fields.computed_field.store = true; // --> Computed and not stored displayed in "Measures" menu
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `<pivot/>`,
|
||
context: {
|
||
pivot_measures: ["__count"],
|
||
},
|
||
mockRPC(route, args) {
|
||
if (args.method === "create_or_replace") {
|
||
assert.deepEqual(args.args[0].context, {
|
||
group_by: [],
|
||
pivot_column_groupby: [],
|
||
pivot_measures: ["computed_field"],
|
||
pivot_row_groupby: [],
|
||
});
|
||
return 1;
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(1, 3).map((el) => el.innerText),
|
||
["Total", "Count"]
|
||
);
|
||
|
||
await toggleMenu(target, "Measures");
|
||
await toggleMenuItem(target, "Count");
|
||
await toggleMenuItem(target, "Computed and not stored");
|
||
|
||
assert.deepEqual(getFacetTexts(target), []);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(1, 3).map((el) => el.innerText),
|
||
["Total", "Computed and not stored"]
|
||
);
|
||
|
||
await toggleSearchBarMenu(target);
|
||
await toggleSaveFavorite(target);
|
||
await editFavoriteName(target, "Favorite");
|
||
await saveFavorite(target);
|
||
|
||
assert.deepEqual(getFacetTexts(target), ["Favorite"]);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("th")].slice(1, 3).map((el) => el.innerText),
|
||
["Total", "Computed and not stored"]
|
||
);
|
||
}
|
||
);
|
||
|
||
QUnit.test("filter -> sort -> unfilter should not crash", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
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,
|
||
},
|
||
});
|
||
|
||
assert.deepEqual(getFacetTexts(target), ["xphone"]);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((el) => el.innerText),
|
||
["Total", "xphone", "Yes"]
|
||
);
|
||
assert.strictEqual(getCurrentValues(target), ["1", "1", "1"].join());
|
||
|
||
await click(target, ".o_pivot_measure_row");
|
||
await toggleSearchBarMenu(target);
|
||
await toggleMenuItem(target, "xphone");
|
||
|
||
assert.deepEqual(getFacetTexts(target), []);
|
||
assert.deepEqual(
|
||
[...target.querySelectorAll("tbody th")].map((el) => el.innerText),
|
||
["Total", "xphone", "Yes", "xpad"]
|
||
);
|
||
assert.strictEqual(getCurrentValues(target), ["4", "1", "1", "3"].join());
|
||
});
|
||
|
||
QUnit.test(
|
||
"no class 'o_view_sample_data' when real data are presented",
|
||
async function (assert) {
|
||
serverData.models.partner.fields.foo.store = true;
|
||
serverData.models.partner.records = [];
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot sample="1">
|
||
<field name="product_id" type="row"/>
|
||
</pivot>
|
||
`,
|
||
});
|
||
|
||
assert.containsOnce(target, ".o_pivot_view .o_view_sample_data");
|
||
assert.containsOnce(target, ".o_pivot_view table");
|
||
await toggleMenu(target, "Measures");
|
||
await toggleMenuItem(target, "Foo");
|
||
assert.containsNone(target, ".o_pivot_view .o_view_sample_data");
|
||
assert.containsNone(target, ".o_pivot_view table");
|
||
}
|
||
);
|
||
|
||
QUnit.test("group by properties in pivot view", async function (assert) {
|
||
assert.expect(18);
|
||
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: "<pivot/>",
|
||
searchViewArch: `
|
||
<search>
|
||
<filter name='group_by_properties' string="Properties" context="{'group_by':'properties'}"/>
|
||
</search>
|
||
`,
|
||
mockRPC(route, args) {
|
||
if (
|
||
route === "/web/dataset/call_kw/partner/web_search_read" &&
|
||
args.kwargs.specification?.properties_definition
|
||
) {
|
||
assert.step("fetch_definition");
|
||
} else if (
|
||
route === "/web/dataset/call_kw/partner/read_group" &&
|
||
args.kwargs.groupby?.includes("properties.my_char")
|
||
) {
|
||
assert.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,
|
||
},
|
||
];
|
||
}
|
||
},
|
||
});
|
||
|
||
assert.strictEqual(target.querySelectorAll(".o_value").length, 1);
|
||
assert.strictEqual(target.querySelector(".o_value").innerText, "4");
|
||
|
||
await click(target, ".border-top-0 span");
|
||
assert.verifySteps([]);
|
||
|
||
const propertiesItem = target.querySelector(".o_accordion_toggle");
|
||
assert.ok(propertiesItem);
|
||
assert.strictEqual(propertiesItem.innerText, "Properties");
|
||
await click(propertiesItem);
|
||
|
||
await nextTick();
|
||
assert.verifySteps(["fetch_definition"]);
|
||
|
||
await click(target, ".o_accordion_values .o_menu_item");
|
||
|
||
await nextTick();
|
||
assert.verifySteps(["read_group"]);
|
||
|
||
const cells = target.querySelectorAll(".o_value");
|
||
assert.strictEqual(cells.length, 4);
|
||
assert.strictEqual(cells[0].innerText, "2");
|
||
assert.strictEqual(cells[1].innerText, "1");
|
||
assert.strictEqual(cells[2].innerText, "1");
|
||
assert.strictEqual(cells[3].innerText, "4");
|
||
|
||
const columns = target.querySelectorAll(".o_pivot_header_cell_closed");
|
||
assert.strictEqual(columns.length, 4);
|
||
assert.strictEqual(columns[0].innerText, "None");
|
||
assert.strictEqual(columns[1].innerText, "aaa");
|
||
assert.strictEqual(columns[2].innerText, "bbb");
|
||
});
|
||
|
||
QUnit.test("avoid duplicates in read_group parameter 'groupby'", async function (assert) {
|
||
await makeView({
|
||
type: "pivot",
|
||
resModel: "partner",
|
||
serverData,
|
||
arch: `
|
||
<pivot sample="1">
|
||
<field name="date" type="row"/>
|
||
<field name="date" type="col" interval="month"/>
|
||
</pivot>
|
||
`,
|
||
mockRPC(_, { method, kwargs }) {
|
||
if (method === "read_group") {
|
||
assert.step(JSON.stringify(kwargs.groupby));
|
||
}
|
||
},
|
||
});
|
||
assert.verifySteps([`[]`, `["date:month"]`, `["date:month"]`, `["date:month"]`]);
|
||
});
|
||
});
|