Files
enterprise-addons/documents_spreadsheet/static/tests/pivot/ui/pivot_view.test.js
T

1320 lines
51 KiB
JavaScript

import {
defineDocumentSpreadsheetModels,
defineDocumentSpreadsheetTestAction,
getBasicData,
getBasicServerData,
getDocumentBasicData,
} from "@documents_spreadsheet/../tests/helpers/data";
import { makeDocumentsSpreadsheetMockEnv } from "@documents_spreadsheet/../tests/helpers/model";
import {
createSpreadsheetFromPivotView,
spawnPivotViewForSpreadsheet,
} from "@documents_spreadsheet/../tests/helpers/pivot_helpers";
import { SpreadsheetAction } from "@documents_spreadsheet/bundle/actions/spreadsheet_action";
import { beforeEach, expect, getFixture, test } from "@odoo/hoot";
import { pointerDown } from "@odoo/hoot-dom";
import { animationFrame } from "@odoo/hoot-mock";
import { Model, constants, helpers } from "@odoo/o-spreadsheet";
import { selectCell, setCellContent } from "@spreadsheet/../tests/helpers/commands";
import { Partner, Product, ResUsers } from "@spreadsheet/../tests/helpers/data";
import {
getCellContent,
getCellValue,
getEvaluatedCell,
getEvaluatedGrid,
} from "@spreadsheet/../tests/helpers/getters";
import { getZoneOfInsertedDataSource } from "@spreadsheet/../tests/helpers/pivot";
import { waitForDataLoaded } from "@spreadsheet/helpers/model";
import {
getSpreadsheetActionModel,
prepareWebClientForSpreadsheet,
} from "@spreadsheet_edition/../tests/helpers/webclient_helpers";
import {
contains,
fields,
getService,
mountView,
mountWithCleanup,
onRpc,
patchWithCleanup,
toggleMenu,
toggleMenuItem,
serverState,
} from "@web/../tests/web_test_helpers";
import { user } from "@web/core/user";
import { WebClient } from "@web/webclient/webclient";
const { sanitizeSheetName, toCartesian, toZone } = helpers;
defineDocumentSpreadsheetModels();
defineDocumentSpreadsheetTestAction();
const { PIVOT_TABLE_CONFIG } = constants;
beforeEach(() => {
ResUsers._records = getDocumentBasicData().models["res.users"].records;
});
function getGridIconEventPosition(model, xc) {
const position = toCartesian(xc);
const sheetId = model.getters.getActiveSheetId();
const icon = model.getters.getCellIcons({ sheetId, ...position })[0];
if (!icon) {
throw new Error(`No icon inside cell ${xc}`);
}
const gridPosition = getFixture().querySelector(".o-grid-overlay").getBoundingClientRect();
const gridOffset = model.getters.getGridOffset();
const rect = model.getters.getCellIconRect(icon, model.getters.getRect(toZone(xc)));
const x = rect.x + rect.width / 2 - gridOffset.x + gridPosition.x;
const y = rect.y + rect.height / 2 - gridOffset.y + +gridPosition.y;
return { x, y };
}
async function clickGridIcon(model, xc) {
const { x, y } = getGridIconEventPosition(model, xc);
await pointerDown(".o-grid-overlay", { position: { x, y } });
}
test("simple pivot export", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="foo" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A1")).toBe("");
expect(getCellContent(model, "A2")).toBe("");
expect(getCellContent(model, "A3")).toBe("=PIVOT.HEADER(1)");
expect(getCellContent(model, "B1")).toBe("=PIVOT.HEADER(1)");
expect(getCellContent(model, "B2")).toBe('=PIVOT.HEADER(1,"measure","foo:sum")');
expect(getCellContent(model, "B3")).toBe('=PIVOT.VALUE(1,"foo:sum")');
});
test.tags("desktop");
test("open side panel in desktop mode", async () => {
await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="foo" type="measure"/>
</pivot>`,
"partner,false,search": /* xml */ `<search/>`,
},
},
});
expect(".o_spreadsheet_pivot_side_panel").toHaveCount(1);
});
test.tags("mobile");
test("don't open side panel in mobile mode", async () => {
await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="foo" type="measure"/>
</pivot>`,
"partner,false,search": /* xml */ `<search/>`,
},
},
});
expect(".o_spreadsheet_pivot_side_panel").toHaveCount(0);
});
test("simple pivot export with two measures", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="foo" type="measure"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "B1")).toBe("=PIVOT.HEADER(1)");
expect(getCellContent(model, "B2")).toBe('=PIVOT.HEADER(1,"measure","foo:sum")');
expect(getCellContent(model, "C2")).toBe('=PIVOT.HEADER(1,"measure","probability:avg")');
expect(getCellContent(model, "B3")).toBe('=PIVOT.VALUE(1,"foo:sum")');
expect(getCellContent(model, "C3")).toBe('=PIVOT.VALUE(1,"probability:avg")');
});
test("Insert in spreadsheet is disabled when data is empty", async () => {
const data = getBasicData();
data.partner.records = [];
data.product.records = [];
await makeDocumentsSpreadsheetMockEnv({ serverData: { models: data } });
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="foo" type="measure"/>
</pivot>`,
});
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", true);
});
test("Insert in spreadsheet is disabled when no measure is specified", async () => {
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="foo" type="measure"/>
</pivot>`,
});
await toggleMenu("Measures");
await toggleMenuItem("Foo");
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", true);
});
test("Insert in spreadsheet is disabled when same groupby occurs in both columns and rows", async () => {
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="id" type="col"/>
<field name="id" type="row"/>
<field name="foo" type="measure"/>
</pivot>`,
});
await toggleMenu("Measures");
await toggleMenuItem("Foo");
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", true);
});
test("Insert in spreadsheet is disabled when columns or rows contain duplicate groupbys", async () => {
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="id" type="col"/>
<field name="bar" type="row"/>
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
});
await toggleMenu("Measures");
await toggleMenuItem("Probability");
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", true);
});
test("Insert in spreadsheet is disabled when columns and rows both contains same groupby with different aggregator", async () => {
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="date" interval="year" type="col"/>
<field name="date" interval="month" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
});
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", true);
});
test("Can insert in spreadsheet when group by the same date fields with different aggregates", async () => {
await mountView({
type: "pivot",
resModel: "partner",
arch: /*xml*/ `
<pivot>
<field name="date" interval="year" type="col"/>
<field name="date" interval="month" type="col"/>
<field name="probability" type="measure"/>
</pivot>`,
});
expect("button.o_pivot_add_spreadsheet").toHaveProperty("disabled", false);
});
test("groupby date field without interval defaults to month", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="foo" type="col"/>
<!-- no interval specified -->
<field name="date" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
const pivot = model.getters.getPivotCoreDefinition(pivotId);
expect(pivot).toEqual({
columns: [{ fieldName: "foo" }],
context: {},
domain: [],
measures: [{ id: "probability:avg", fieldName: "probability", aggregator: "avg" }],
model: "partner",
rows: [{ fieldName: "date", granularity: "month" }],
name: "Partners by Foo",
type: "ODOO",
});
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"date:month",DATE(2016,4,1))');
expect(getCellContent(model, "A4")).toBe('=PIVOT.HEADER(1,"date:month",DATE(2016,10,1))');
expect(getCellContent(model, "A5")).toBe('=PIVOT.HEADER(1,"date:month",DATE(2016,12,1))');
expect(getCellContent(model, "B3")).toBe(
'=PIVOT.VALUE(1,"probability:avg","date:month",DATE(2016,4,1),"foo",1)'
);
expect(getCellContent(model, "B4")).toBe(
'=PIVOT.VALUE(1,"probability:avg","date:month",DATE(2016,10,1),"foo",1)'
);
expect(getCellContent(model, "B5")).toBe(
'=PIVOT.VALUE(1,"probability:avg","date:month",DATE(2016,12,1),"foo",1)'
);
expect(getEvaluatedCell(model, "A3").formattedValue).toBe("April 2016");
expect(getEvaluatedCell(model, "A4").formattedValue).toBe("October 2016");
expect(getEvaluatedCell(model, "A5").formattedValue).toBe("December 2016");
expect(getEvaluatedCell(model, "B3").formattedValue).toBe("");
expect(getEvaluatedCell(model, "B4").formattedValue).toBe("11.00");
expect(getEvaluatedCell(model, "B5").formattedValue).toBe("");
setCellContent(model, "B4", '=PIVOT.VALUE(1,"probability:avg","date",DATE(2016,10,1),"foo",1)');
expect(getEvaluatedCell(model, "B4").formattedValue).toBe("11.00");
});
test("pivot with one level of group bys", async () => {
const { model } = await createSpreadsheetFromPivotView();
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"bar",FALSE)');
expect(getCellContent(model, "A4")).toBe('=PIVOT.HEADER(1,"bar",TRUE)');
expect(getCellContent(model, "A5")).toBe("=PIVOT.HEADER(1)");
expect(getCellContent(model, "B2")).toBe(
'=PIVOT.HEADER(1,"foo",1,"measure","probability:avg")'
);
expect(getCellContent(model, "C3")).toBe(
'=PIVOT.VALUE(1,"probability:avg","bar",FALSE,"foo",2)'
);
expect(getCellContent(model, "F5")).toBe('=PIVOT.VALUE(1,"probability:avg")');
});
test("groupby date field on row gives correct name", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="date" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
const pivot = model.getters.getPivotCoreDefinition(pivotId);
expect(pivot).toEqual({
columns: [],
context: {},
domain: [],
measures: [{ id: "probability:avg", fieldName: "probability", aggregator: "avg" }],
model: "partner",
rows: [{ fieldName: "date", granularity: "month" }],
name: "Partners by Date",
type: "ODOO",
});
});
test("pivot with two levels of group bys in rows", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="bar" type="row"/>
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"bar",FALSE)');
expect(getCellContent(model, "A4")).toBe('=PIVOT.HEADER(1,"bar",FALSE,"product_id",41)');
expect(getCellContent(model, "A5")).toBe('=PIVOT.HEADER(1,"bar",TRUE)');
expect(getCellContent(model, "A6")).toBe('=PIVOT.HEADER(1,"bar",TRUE,"product_id",37)');
expect(getCellContent(model, "A7")).toBe('=PIVOT.HEADER(1,"bar",TRUE,"product_id",41)');
expect(getCellContent(model, "A8")).toBe("=PIVOT.HEADER(1)");
});
test("verify that there is a record for an undefined many2one header", async () => {
const data = getBasicData();
data.partner.records = [
{
id: 1,
foo: 12,
bar: true,
date: "2016-04-14",
product_id: false,
probability: 10,
},
];
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: data,
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"product_id",FALSE)');
});
test("undefined date is inserted in pivot", async () => {
const data = getBasicData();
data.partner.records = [
{
id: 1,
foo: 12,
bar: true,
date: false,
product_id: 37,
probability: 10,
},
];
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: data,
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="date" interval="day" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"date:day",FALSE)');
});
test("pivot with two levels of group bys in cols", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="bar" type="col"/>
<field name="product_id" type="col"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A1")).toBe("");
expect(getCellContent(model, "B1")).toBe('=PIVOT.HEADER(1,"bar",FALSE)');
expect(getCellContent(model, "B2")).toBe('=PIVOT.HEADER(1,"bar",FALSE,"product_id",41)');
expect(getCellContent(model, "B3")).toBe(
'=PIVOT.HEADER(1,"bar",FALSE,"product_id",41,"measure","probability:avg")'
);
expect(getCellContent(model, "C1")).toBe('=PIVOT.HEADER(1,"bar",TRUE)');
expect(getCellContent(model, "C2")).toBe('=PIVOT.HEADER(1,"bar",TRUE,"product_id",37)');
expect(getCellContent(model, "C3")).toBe(
'=PIVOT.HEADER(1,"bar",TRUE,"product_id",37,"measure","probability:avg")'
);
expect(getCellContent(model, "D2")).toBe('=PIVOT.HEADER(1,"bar",TRUE,"product_id",41)');
expect(getCellContent(model, "D3")).toBe(
'=PIVOT.HEADER(1,"bar",TRUE,"product_id",41,"measure","probability:avg")'
);
});
test("pivot with count as measure", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="probability" type="measure"/>
</pivot>`,
},
},
actions: async (target) => {
await toggleMenu("Measures");
await toggleMenuItem("Count");
},
});
expect(getCellContent(model, "C2")).toBe('=PIVOT.HEADER(1,"measure","__count")');
expect(getCellContent(model, "C3")).toBe('=PIVOT.VALUE(1,"__count")');
});
test("pivot with two levels of group bys in cols with not enough cols", async () => {
const data = getBasicData();
// add many values in a subgroup
for (let i = 0; i < 70; i++) {
Product._records.push({
id: i + 9999,
display_name: i.toString(),
});
Partner._records.push({
id: i + 9999,
bar: i % 2 === 0,
product_id: i + 9999,
probability: i,
});
}
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: data,
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="bar" type="col"/>
<field name="product_id" type="col"/>
<field name="foo" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
// 72 products * 1 groups + 1 row header + 1 total col
expect(model.getters.getNumberCols(model.getters.getActiveSheetId())).toBe(75);
});
test("groupby week is sorted", async () => {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="foo" type="col"/>
<field name="date" interval="week" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A3")).toBe(`=PIVOT.HEADER(1,"date:week","15/2016")`);
expect(getCellContent(model, "A4")).toBe(`=PIVOT.HEADER(1,"date:week","43/2016")`);
expect(getCellContent(model, "A5")).toBe(`=PIVOT.HEADER(1,"date:week","49/2016")`);
expect(getCellContent(model, "A6")).toBe(`=PIVOT.HEADER(1,"date:week","50/2016")`);
});
test("Can save a pivot in a new spreadsheet", async () => {
const serverData = {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="probability" type="measure"/>
</pivot>`,
},
};
await prepareWebClientForSpreadsheet();
await makeDocumentsSpreadsheetMockEnv({
serverData,
mockRPC: function (route, args) {
if (route.includes("get_spreadsheets_to_display")) {
return [{ id: 1, name: "My Spreadsheet" }];
}
if (args.method === "action_open_new_spreadsheet") {
expect.step("action_open_new_spreadsheet");
}
},
});
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "pivot"]],
});
const target = getFixture();
const insertButton = target.querySelector("button.o_pivot_add_spreadsheet");
expect(insertButton.parentElement.dataset.tooltip).toBe(undefined);
await contains(insertButton).click();
await contains(".modal-content > .modal-footer > .btn-primary").click();
expect.verifySteps(["action_open_new_spreadsheet"]);
});
test.tags("desktop");
test("Can save a pivot in existing spreadsheet", async () => {
const serverData = {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="probability" type="measure"/>
</pivot>`,
},
};
await prepareWebClientForSpreadsheet();
onRpc("/web/action/load", async () => {
expect.step("write");
return { id: 1, type: "ir.actions.act_window_close" };
});
onRpc("/spreadsheet/data/*", () => expect.step("/spreadsheet/data/"));
await makeDocumentsSpreadsheetMockEnv({
serverData,
mockRPC: function (route, args) {
if (args.model === "documents.document") {
switch (args.method) {
case "get_spreadsheets_to_display":
return [{ id: 1, name: "My Spreadsheet" }];
}
}
},
});
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "pivot"]],
});
await contains(".o_pivot_add_spreadsheet").click();
await contains(".o-spreadsheet-grid div[data-id='2']").click();
await contains(".modal-content > .modal-footer > .btn-primary").click();
await animationFrame(); // Wait for the mounted to be executed
expect(".o_spreadsheet_pivot_side_panel").toHaveCount(1);
await getService("action").doAction(1); // leave the spreadsheet action
expect.verifySteps(["/spreadsheet/data/", "write"]);
});
test("Add pivot sheet at the end of existing sheets", async () => {
const model = new Model();
model.dispatch("CREATE_SHEET", { sheetId: "42", position: 1, name: "My Sheet" });
const models = getBasicData();
models["documents.document"].records = [
{
spreadsheet_data: JSON.stringify(model.exportData()),
name: "a spreadsheet",
folder_id: false,
handler: "spreadsheet",
id: 456,
is_favorited: false,
},
];
const serverData = {
models: models,
views: getBasicServerData().views,
};
await prepareWebClientForSpreadsheet();
await makeDocumentsSpreadsheetMockEnv({ serverData });
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "pivot"]],
});
const fixture = getFixture();
await contains(".o_pivot_add_spreadsheet").click();
await contains(".o-spreadsheet-grid div[data-id='456']").click();
await contains(".modal-content > .modal-footer > .btn-primary").click();
await animationFrame(); // Wait for the mounted to be executed
expect(".o-sheet").toHaveCount(3, { message: "it should have a third sheet" });
const sheets = fixture.querySelectorAll(".o-sheet");
const activeSheet = fixture.querySelector(".o-sheet.active");
expect(activeSheet).toBe(sheets[2]);
expect(sheets[0]).toHaveText("Sheet1");
expect(sheets[1]).toHaveText("My Sheet");
expect(sheets[2]).toHaveText("Partners by Foo (Pivot #1)");
});
test("Add pivot in spreadsheet with already the same sheet name", async () => {
const model = new Model();
const activeSheetId = model.getters.getActiveSheetId();
model.dispatch("RENAME_SHEET", {
sheetId: model.getters.getActiveSheetId(),
oldName: model.getters.getSheetName(activeSheetId),
newName: "Partners by Foo (Pivot #1)",
});
const models = getBasicData();
models["documents.document"].records = [
{
spreadsheet_data: JSON.stringify(model.exportData()),
name: "a spreadsheet",
folder_id: false,
handler: "spreadsheet",
id: 456,
is_favorited: false,
},
];
const serverData = {
models: models,
views: getBasicServerData().views,
};
await prepareWebClientForSpreadsheet();
await makeDocumentsSpreadsheetMockEnv({ serverData });
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "partner",
type: "ir.actions.act_window",
views: [[false, "pivot"]],
});
const fixture = getFixture();
await contains(".o_pivot_add_spreadsheet").click();
await contains(".o-spreadsheet-grid div[data-id='456']").click();
await contains(".modal-content > .modal-footer > .btn-primary").click();
await animationFrame();
expect(".o-sheet").toHaveCount(2, { message: "it should have a second sheet" });
const sheets = fixture.querySelectorAll(".o-sheet");
expect(sheets[0]).toHaveText("Partners by Foo (Pivot #1)");
expect(sheets[1]).toHaveText("Sheet1");
});
test("pivot with a domain", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
domain: [["bar", "=", true]],
});
const domain = model.getters.getPivotCoreDefinition(pivotId).domain;
expect(domain).toEqual([["bar", "=", true]], {
message: "It should have the correct domain",
});
expect(getCellContent(model, "A3")).toBe(`=PIVOT.HEADER(1,"bar",TRUE)`);
expect(getCellContent(model, "A4")).toBe(`=PIVOT.HEADER(1)`);
});
test("pivot with a contextual domain", async () => {
const uid = user.userId;
const serverData = getBasicServerData();
serverData.models.partner.records = [
{
id: 1,
probability: 0.5,
foo: uid,
bar: true,
},
];
serverData.views["partner,false,search"] = /* xml */ `
<search>
<filter string="Filter" name="filter" domain="[('foo', '=', uid)]"/>
</search>
`;
serverData.views["partner,false,pivot"] = /* xml */ `
<pivot>
<field name="probability" type="measure"/>
</pivot>
`;
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData,
additionalContext: { search_default_filter: 1 },
mockRPC: function (route, args) {
if (args.method === "formatted_read_grouping_sets") {
expect(args.kwargs.domain).toEqual([["foo", "=", uid]], {
message: "data should be fetched with the evaluated the domain",
});
expect.step("formatted_read_grouping_sets");
}
},
});
const domain = model.getters.getPivotCoreDefinition(pivotId).domain;
expect(domain).toBe('[("foo", "=", uid)]', {
message: "It should have the raw domain string",
});
expect(model.exportData().pivots[pivotId].domain).toBe('[("foo", "=", uid)]', {
message: "domain is exported with the dynamic value",
});
expect.verifySteps(["formatted_read_grouping_sets", "formatted_read_grouping_sets"]);
});
test("pivot with a quote in name", async function () {
const data = getBasicData();
Product._records.push({
id: 42,
display_name: `name with "`,
});
const { model } = await createSpreadsheetFromPivotView({
model: "product",
serverData: {
models: data,
views: {
"product,false,pivot": /*xml*/ `
<pivot>
<field name="display_name" type="col"/>
<field name="id" type="row"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "B1")).toBe(`=PIVOT.HEADER(1,"display_name","name with \\"")`);
});
test("group by related field with archived record", async function () {
// TODOAFTERSPLIT this doesn't seem to have any archived record
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="product_id" type="col"/>
<field name="name" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "B1")).toBe(`=PIVOT.HEADER(1,"product_id",37)`);
expect(getCellContent(model, "C1")).toBe(`=PIVOT.HEADER(1,"product_id",41)`);
expect(getCellContent(model, "D1")).toBe(`=PIVOT.HEADER(1)`);
});
test("group by regular field with archived record", async function () {
Partner._records[0].active = false;
const { model } = await createSpreadsheetFromPivotView({
serverData: {
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="product_id" type="col"/>
<field name="foo" type="row"/>
<field name="probability" type="measure"/>
</pivot>`,
},
},
});
expect(getCellContent(model, "A3")).toBe(`=PIVOT.HEADER(1,"foo",1)`);
expect(getCellContent(model, "A4")).toBe(`=PIVOT.HEADER(1,"foo",2)`);
expect(getCellContent(model, "A5")).toBe(`=PIVOT.HEADER(1,"foo",17)`);
expect(getCellContent(model, "A6")).toBe(`=PIVOT.HEADER(1)`);
});
test("Columns of newly inserted pivot are auto-resized", async function () {
const data = getBasicData();
Partner._fields.probability = fields.Float({
string: "Probability with a super long name",
searchable: true,
store: true,
aggregator: "avg",
groupable: false,
});
const { model } = await createSpreadsheetFromPivotView({
serverData: {
...getBasicServerData(),
models: data,
},
});
const sheetId = model.getters.getActiveSheetId();
const defaultColSize = 96;
expect(model.getters.getColSize(sheetId, 1) > defaultColSize).toBe(true, {
message: "Column should be resized",
});
});
test("user related context is not saved in the spreadsheet", async function () {
serverState.companies = [{ id: 15, name: "Hermit" }];
const userCtx = user.context;
patchWithCleanup(user, {
get context() {
return Object.assign({}, userCtx, {
allowed_company_ids: [15],
tz: "bx",
lang: "FR",
uid: 4,
});
},
});
patchWithCleanup(user, { userId: 4 });
const context = {
...user.context,
default_stage_id: 5,
};
const { model, pivotId } = await createSpreadsheetFromPivotView({
additionalContext: context,
});
expect(model.exportData().pivots[pivotId].context).toEqual(
{
default_stage_id: 5,
},
{ message: "user related context is not stored in context" }
);
});
test("pivot related context is not saved in the spreadsheet", async function () {
const context = {
pivot_row_groupby: ["foo"],
pivot_column_groupby: ["bar"],
pivot_measures: ["probability"],
default_stage_id: 5,
};
const { model, pivotId } = await createSpreadsheetFromPivotView({
additionalContext: context,
actions: async (target) => {
await toggleMenu("Measures");
await toggleMenuItem("Count");
},
mockRPC: function (route, args) {
if (args.method === "formatted_read_grouping_sets") {
expect.step(args.kwargs.aggregates.join(","));
}
},
});
expect.verifySteps([
// initial view
"probability:avg,__count",
// adding count in the view
"probability:avg,__count",
// loaded in the spreadsheet
"probability:avg,__count",
]);
expect(model.exportData().pivots[pivotId].context).toEqual(
{
default_stage_id: 5,
},
{ message: "pivot related context is not stored in context" }
);
});
test("sort first pivot column (ascending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
actions: async (target) => {
await contains("thead .o_pivot_measure_row").click();
},
});
expect(getCellValue(model, "A3")).toBe("No");
expect(getCellValue(model, "A4")).toBe("Yes");
expect(getCellValue(model, "B3")).toBe("");
expect(getCellValue(model, "B4")).toBe(11);
expect(getCellValue(model, "C3")).toBe(15);
expect(getCellValue(model, "C4")).toBe("");
expect(getCellValue(model, "F3")).toBe(15);
expect(getCellValue(model, "F4")).toBe(116);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [{ field: "foo", value: 1, type: "integer" }],
measure: "probability:avg",
order: "asc",
});
});
test("sort first pivot column (descending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
actions: async (target) => {
await contains("thead .o_pivot_measure_row").click(); // first click toggles ascending
await contains("thead .o_pivot_measure_row").click(); // second is descending
},
});
expect(getCellValue(model, "A3")).toBe("Yes");
expect(getCellValue(model, "A4")).toBe("No");
expect(getCellValue(model, "B3")).toBe(11);
expect(getCellValue(model, "B4")).toBe("");
expect(getCellValue(model, "C3")).toBe("");
expect(getCellValue(model, "C4")).toBe(15);
expect(getCellValue(model, "F3")).toBe(116);
expect(getCellValue(model, "F4")).toBe(15);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [{ field: "foo", value: 1, type: "integer" }],
measure: "probability:avg",
order: "desc",
});
});
test("sort second pivot column (ascending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
actions: async (target) => {
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click();
},
});
expect(getCellValue(model, "A3")).toBe("Yes");
expect(getCellValue(model, "A4")).toBe("No");
expect(getCellValue(model, "B3")).toBe(11);
expect(getCellValue(model, "B4")).toBe("");
expect(getCellValue(model, "C3")).toBe("");
expect(getCellValue(model, "C4")).toBe(15);
expect(getCellValue(model, "F3")).toBe(116);
expect(getCellValue(model, "F4")).toBe(15);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [{ field: "foo", value: 2, type: "integer" }],
measure: "probability:avg",
order: "asc",
});
});
test("sort second pivot column (descending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
actions: async (target) => {
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click(); // first click toggles ascending
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click(); // second is descending
},
});
expect(getCellValue(model, "A3")).toBe("No");
expect(getCellValue(model, "A4")).toBe("Yes");
expect(getCellValue(model, "B3")).toBe("");
expect(getCellValue(model, "B4")).toBe(11);
expect(getCellValue(model, "C3")).toBe(15);
expect(getCellValue(model, "C4")).toBe("");
expect(getCellValue(model, "F3")).toBe(15);
expect(getCellValue(model, "F4")).toBe(116);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [{ field: "foo", value: 2, type: "integer" }],
measure: "probability:avg",
order: "desc",
});
});
test("sort second pivot measure (ascending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
<field name="foo" type="measure"/>
</pivot>`,
},
},
actions: async (target) => {
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click();
},
});
expect(getCellValue(model, "A3")).toBe("xphone");
expect(getCellValue(model, "A4")).toBe("xpad");
expect(getCellValue(model, "B3")).toBe(10);
expect(getCellValue(model, "B4")).toBe(121);
expect(getCellValue(model, "C3")).toBe(12);
expect(getCellValue(model, "C4")).toBe(20);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [],
measure: "foo:sum",
order: "asc",
});
});
test("sort second pivot measure (descending)", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot string="Partners">
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
<field name="foo" type="measure"/>
</pivot>`,
},
},
actions: async (target) => {
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click();
await contains(target.querySelectorAll("thead .o_pivot_measure_row")[1]).click();
},
});
expect(getCellValue(model, "A3")).toBe("xpad");
expect(getCellValue(model, "A4")).toBe("xphone");
expect(getCellValue(model, "B3")).toBe(121);
expect(getCellValue(model, "B4")).toBe(10);
expect(getCellValue(model, "C3")).toBe(20);
expect(getCellValue(model, "C4")).toBe(12);
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toEqual({
domain: [],
measure: "foo:sum",
order: "desc",
});
});
test("sorting on a date column works", async () => {
patchWithCleanup(user, { tz: "UTC" });
const { model, pivotId } = await createSpreadsheetFromPivotView({
actions: async (target) => {
await contains("thead .o_pivot_measure_row").click();
},
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot default_order="probability desc">
<field name="create_date" interval="year" type="col"/>
<field name="create_date" interval="quarter" type="col"/>
<field name="create_date" interval="month" type="col"/>
<field name="create_date" interval="week" type="col"/>
<field name="create_date" interval="day" type="col"/>
<field name="product_id" type="row"/>
<field name="foo" type="measure"/>
</pivot>`,
},
},
});
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn.domain).toMatchObject([
{ field: "create_date:year", value: 2006 },
{ field: "create_date:quarter", value: "4/2005" },
{ field: "create_date:month", value: "12/2005" },
{ field: "create_date:week", value: "1/2006" },
{ field: "create_date:day", value: 38719 },
]);
});
test("remove sorting if measure is removed", async () => {
const { model, pivotId } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot default_order="probability desc">
<field name="product_id" type="row"/>
<field name="probability" type="measure"/>
<field name="foo" type="measure"/>
</pivot>`,
},
},
actions: async (target) => {
await toggleMenu("Measures");
await toggleMenuItem("Probability"); // remove probability measure
},
});
expect(model.getters.getPivotCoreDefinition(pivotId).sortedColumn).toBe(undefined);
});
test("search view with group by and additional row group", async () => {
const { model } = await createSpreadsheetFromPivotView({
additionalContext: { search_default_group_name: true },
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
</pivot>`,
"partner,false,search": /* xml */ `
<search>
<group>
<filter name="group_name" context="{'group_by':'name'}"/>
<filter name="group_foo" context="{'group_by':'foo'}"/>
</group>
</search>
`,
},
},
actions: async (target) => {
await contains(target.querySelectorAll("tbody .o_pivot_header_cell_closed")[0]).click();
// group by foo
await contains(".dropdown-menu span:nth-child(2)").click();
},
});
expect(getCellContent(model, "A1")).toBe("");
expect(getCellContent(model, "A2")).toBe("");
expect(getCellContent(model, "A3")).toBe('=PIVOT.HEADER(1,"name","Raoul")');
expect(getCellContent(model, "A4")).toBe('=PIVOT.HEADER(1,"name","Raoul","foo",12)');
expect(getCellContent(model, "A5")).toBe('=PIVOT.HEADER(1,"name","Steven")');
expect(getCellContent(model, "A6")).toBe('=PIVOT.HEADER(1,"name","Steven","foo",1)');
expect(getCellContent(model, "A7")).toBe('=PIVOT.HEADER(1,"name","Taylor")');
expect(getCellContent(model, "A8")).toBe('=PIVOT.HEADER(1,"name","Taylor","foo",17)');
expect(getCellContent(model, "A9")).toBe('=PIVOT.HEADER(1,"name","Zara")');
expect(getCellContent(model, "A10")).toBe('=PIVOT.HEADER(1,"name","Zara","foo",2)');
expect(getCellContent(model, "B2")).toBe('=PIVOT.HEADER(1,"measure","__count")');
});
test("Pivot name can be changed from the dialog", async () => {
await spawnPivotViewForSpreadsheet();
let spreadsheetAction;
patchWithCleanup(SpreadsheetAction.prototype, {
setup() {
super.setup();
spreadsheetAction = this;
},
});
await contains(document.body.querySelector(".o_pivot_add_spreadsheet")).click();
await contains(".o-sp-dialog-meta-name .o_input").edit("New name");
await contains(".modal-content > .modal-footer > .btn-primary").click();
const model = getSpreadsheetActionModel(spreadsheetAction);
await waitForDataLoaded(model);
const pivotId = model.getters.getPivotIds()[0];
expect(model.getters.getPivotName(pivotId)).toBe("New name");
expect(model.getters.getPivotDisplayName(pivotId)).toBe("(#1) New name");
});
test("Pivot name is not changed if the name is empty", async () => {
await spawnPivotViewForSpreadsheet();
let spreadsheetAction;
patchWithCleanup(SpreadsheetAction.prototype, {
setup() {
super.setup();
spreadsheetAction = this;
},
});
await contains(document.body.querySelector(".o_pivot_add_spreadsheet")).click();
document.body.querySelector(".o-sp-dialog-meta-name .o_input").value = "";
await contains(".modal-content > .modal-footer > .btn-primary").click();
await animationFrame();
const model = getSpreadsheetActionModel(spreadsheetAction);
await waitForDataLoaded(model);
const pivotId = model.getters.getPivotIds()[0];
expect(model.getters.getPivotName(pivotId)).toBe("Partners by Foo");
});
test("Sheet is created when pivot name contains invalid characters", async () => {
await spawnPivotViewForSpreadsheet();
let spreadsheetAction;
patchWithCleanup(SpreadsheetAction.prototype, {
setup() {
super.setup();
spreadsheetAction = this;
},
});
await contains(document.body.querySelector(".o_pivot_add_spreadsheet")).click();
const pivotName = "Do not keep Unsupported characters: '-:-*-?-\\-[-]-/";
await contains(".o-sp-dialog-meta-name .o_input").edit(pivotName);
await contains(".modal-content > .modal-footer > .btn-primary").click();
const model = getSpreadsheetActionModel(spreadsheetAction);
await waitForDataLoaded(model);
const pivotId = model.getters.getPivotIds()[0];
expect(model.getters.getPivotName(pivotId)).toBe(pivotName);
expect(model.getters.getPivotDisplayName(pivotId)).toBe(`(#1) ${pivotName}`);
const sanitizedSheetName = sanitizeSheetName(pivotName);
expect(model.getters.getSheetName(model.getters.getActiveSheetId())).toBe(
`${sanitizedSheetName} (Pivot #1)`
);
});
test("Check pivot measures with m2o field", async function () {
const data = getBasicData();
Partner._records.push(
{ active: true, id: 5, foo: 12, bar: true, product_id: 37, probability: 50 },
{ active: true, id: 6, foo: 17, bar: true, product_id: 41, probability: 12 },
{ active: true, id: 7, foo: 17, bar: true, product_id: 37, probability: 13 },
{ active: true, id: 8, foo: 17, bar: true, product_id: 37, probability: 14 }
);
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: data,
views: {
"partner,false,pivot": `
<pivot string="Partners">
<field name="foo" type="col"/>
<field name="bar" type="row"/>
<field name="product_id" type="measure"/>
</pivot>`,
},
},
});
expect(getCellValue(model, "B4")).toBe(1, {
message: "[Cell B3] There is one distinct product for 'foo - 1' and 'bar - true'",
});
expect(getCellValue(model, "D4")).toBe(1, {
message: "[Cell C3] There is one distinct product for 'foo - 12' and 'bar - true'",
});
expect(getCellValue(model, "E4")).toBe(2, {
message: "[Cell D3] There are two distinct products for 'foo - 17' and 'bar - true'",
});
});
test("Pivot export from an action with an xml ID", async function () {
const actionXmlId = "spreadsheet.partner_action";
const { model, pivotId } = await createSpreadsheetFromPivotView({ actionXmlId });
expect(model.getters.getPivotCoreDefinition(pivotId).actionXmlId).toEqual(
"spreadsheet.partner_action"
);
});
test("Test Autofill component", async function () {
const { model } = await createSpreadsheetFromPivotView();
selectCell(model, "A3");
setCellContent(model, "B3", "");
await pointerDown(".o-autofill-handler");
await animationFrame();
// dispatching the underlying command by hand because the dragndrop in o_spreadsheet.js
// does not react well to the events
model.dispatch("AUTOFILL_SELECT", { col: 1, row: 2 });
await animationFrame();
expect(".o-autofill-nextvalue:first").toHaveText("1");
expect(getCellContent(model, "B2")).toBe(
`=PIVOT.HEADER(1,"foo",1,"measure","probability:avg")`
);
});
test("Inserted pivot is inserted with a table", async function () {
const { model } = await createSpreadsheetFromPivotView();
const sheetId = model.getters.getActiveSheetId();
const [pivotId] = model.getters.getPivotIds();
const pivotZone = getZoneOfInsertedDataSource(model, "pivot", pivotId);
const tables = model.getters.getTables(sheetId);
expect(tables.length).toBe(1);
expect(tables[0].range.zone).toEqual(pivotZone);
expect(tables[0].type).toBe("static");
expect(tables[0].config).toEqual({ ...PIVOT_TABLE_CONFIG, numberOfHeaders: 1 });
});
test("The table has the correct number of headers when inserting a pivot", async function () {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="date" interval="year" type="col"/>
<field name="date" interval="month" type="col"/>
<field name="date" interval="day" type="col"/>
<field name="probability" type="row"/>
<field name="foo" type="measure"/>
</pivot>`,
},
},
});
const sheetId = model.getters.getActiveSheetId();
const tables = model.getters.getTables(sheetId);
const [pivotId] = model.getters.getPivotIds();
const pivotZone = getZoneOfInsertedDataSource(model, "pivot", pivotId);
expect(tables[0].range.zone).toEqual(pivotZone);
expect(tables[0].type).toBe("static");
expect(tables[0].config.numberOfHeaders).toBe(3);
});
test("Can collapse pivot header group", async function () {
const { model } = await createSpreadsheetFromPivotView({
serverData: {
models: getBasicData(),
views: {
"partner,false,pivot": /* xml */ `
<pivot>
<field name="date" interval="year" type="col"/>
<field name="date" interval="month" type="col"/>
<field name="probability" type="measure"/>
</pivot>`,
"partner,false,search": /* xml */ `<search/>`,
},
},
});
selectCell(model, "A20");
await animationFrame();
setCellContent(model, "A20", "=PIVOT(1)");
await clickGridIcon(model, "B20");
const [pivotId] = model.getters.getPivotIds();
const definition = model.getters.getPivotCoreDefinition(pivotId);
expect(definition.collapsedDomains).toEqual({
COL: [[{ field: "date:year", value: 2016, type: "date" }]],
ROW: [],
});
// prettier-ignore
expect(getEvaluatedGrid(model, "A20:C23")).toEqual([
["Untitled by Date (Year)", 2016, ""],
["", "", "Total"],
["", "Probability", "Probability"],
["Total", 131, 131],
]);
});