/** @odoo-module */ import { getCell, getCellContent, getCellFormula, getCellValue, } from "@spreadsheet/../tests/utils/getters"; import { createSpreadsheetWithPivot } from "@spreadsheet/../tests/utils/pivot"; import CommandResult from "@spreadsheet/o_spreadsheet/cancelled_reason"; import { addGlobalFilter, setCellContent } from "@spreadsheet/../tests/utils/commands"; import { createModelWithDataSource, waitForDataSourcesLoaded, } from "@spreadsheet/../tests/utils/model"; import { makeDeferred, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; import { session } from "@web/session"; import { RPCError } from "@web/core/network/rpc_service"; import { getBasicServerData } from "../../utils/data"; QUnit.module("spreadsheet > pivot plugin", {}, () => { QUnit.test("can select a Pivot from cell formula", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); }); QUnit.test( "can select a Pivot from cell formula with '-' before the formula", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); model.dispatch("SET_VALUE", { xc: "C3", text: `=-PIVOT("1","probability","bar","false","foo","2")`, }); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); } ); QUnit.test( "can select a Pivot from cell formula with other numerical values", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); model.dispatch("SET_VALUE", { xc: "C3", text: `=3*PIVOT("1","probability","bar","false","foo","2")+2`, }); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); } ); QUnit.test( "can select a Pivot from cell formula where pivot is in a function call", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); model.dispatch("SET_VALUE", { xc: "C3", text: `=SUM(PIVOT("1","probability","bar","false","foo","2"),PIVOT("1","probability","bar","false","foo","2"))`, }); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); } ); QUnit.test( "can select a Pivot from cell formula where the id is a reference", async function (assert) { const { model } = await createSpreadsheetWithPivot(); setCellContent(model, "C3", `=ODOO.PIVOT(G10,"probability","bar","false","foo","2")+2`); setCellContent(model, "G10", "1"); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); } ); QUnit.test( "can select a Pivot from cell formula (Mix of test scenarios above)", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); model.dispatch("SET_VALUE", { xc: "C3", text: `=3*SUM(PIVOT("1","probability","bar","false","foo","2"),PIVOT("1","probability","bar","false","foo","2"))+2*PIVOT("1","probability","bar","false","foo","2")`, }); const sheetId = model.getters.getActiveSheetId(); const pivotId = model.getters.getPivotIdFromPosition(sheetId, 2, 2); model.dispatch("SELECT_PIVOT", { pivotId }); const selectedPivotId = model.getters.getSelectedPivotId(); assert.strictEqual(selectedPivotId, "1"); } ); QUnit.test("Can remove a pivot with undo after editing a cell", async function (assert) { const { model } = await createSpreadsheetWithPivot(); assert.ok(getCellContent(model, "B1").startsWith("=ODOO.PIVOT.HEADER")); setCellContent(model, "G10", "should be undoable"); model.dispatch("REQUEST_UNDO"); assert.equal(getCellContent(model, "G10"), ""); // 2 REQUEST_UNDO because of the AUTORESIZE feature model.dispatch("REQUEST_UNDO"); model.dispatch("REQUEST_UNDO"); assert.equal(getCellContent(model, "B1"), ""); assert.equal(model.getters.getPivotIds().length, 0); }); QUnit.test("rename pivot with empty name is refused", async (assert) => { const { model } = await createSpreadsheetWithPivot(); const result = model.dispatch("RENAME_ODOO_PIVOT", { pivotId: "1", name: "", }); assert.deepEqual(result.reasons, [CommandResult.EmptyName]); }); QUnit.test("rename pivot with incorrect id is refused", async (assert) => { const { model } = await createSpreadsheetWithPivot(); const result = model.dispatch("RENAME_ODOO_PIVOT", { pivotId: "invalid", name: "name", }); assert.deepEqual(result.reasons, [CommandResult.PivotIdNotFound]); }); QUnit.test("Undo/Redo for RENAME_ODOO_PIVOT", async function (assert) { const { model } = await createSpreadsheetWithPivot(); assert.equal(model.getters.getPivotName("1"), "Partner Pivot"); model.dispatch("RENAME_ODOO_PIVOT", { pivotId: "1", name: "test" }); assert.equal(model.getters.getPivotName("1"), "test"); model.dispatch("REQUEST_UNDO"); assert.equal(model.getters.getPivotName("1"), "Partner Pivot"); model.dispatch("REQUEST_REDO"); assert.equal(model.getters.getPivotName("1"), "test"); }); QUnit.test("Can delete pivot", async function (assert) { const { model } = await createSpreadsheetWithPivot(); model.dispatch("REMOVE_PIVOT", { pivotId: "1" }); assert.strictEqual(model.getters.getPivotIds().length, 0); const B4 = getCell(model, "B4"); assert.equal(B4.evaluated.error.message, `There is no pivot with id "1"`); assert.equal(B4.evaluated.value, `#ERROR`); }); QUnit.test("Can undo/redo a delete pivot", async function (assert) { const { model } = await createSpreadsheetWithPivot(); const value = getCell(model, "B4").evaluated.value; model.dispatch("REMOVE_PIVOT", { pivotId: "1" }); model.dispatch("REQUEST_UNDO"); assert.strictEqual(model.getters.getPivotIds().length, 1); let B4 = getCell(model, "B4"); assert.equal(B4.evaluated.error, undefined); assert.equal(B4.evaluated.value, value); model.dispatch("REQUEST_REDO"); assert.strictEqual(model.getters.getPivotIds().length, 0); B4 = getCell(model, "B4"); assert.equal(B4.evaluated.error.message, `There is no pivot with id "1"`); assert.equal(B4.evaluated.value, `#ERROR`); }); QUnit.test("Format header displays an error for non-existing field", async function (assert) { const { model } = await createSpreadsheetWithPivot(); setCellContent(model, "G10", `=ODOO.PIVOT.HEADER("1", "measure", "non-existing")`); setCellContent(model, "G11", `=ODOO.PIVOT.HEADER("1", "non-existing", "bla")`); await nextTick(); assert.equal(getCellValue(model, "G10"), "#ERROR"); assert.equal(getCellValue(model, "G11"), "#ERROR"); assert.equal( getCell(model, "G10").evaluated.error.message, "Field non-existing does not exist" ); assert.equal( getCell(model, "G11").evaluated.error.message, "Field non-existing does not exist" ); }); QUnit.test( "user context is combined with pivot context to fetch data", async function (assert) { const context = { allowed_company_ids: [15], tz: "bx", lang: "FR", uid: 4, }; const testSession = { uid: 4, user_companies: { allowed_companies: { 15: { id: 15, name: "Hermit" }, 16: { id: 16, name: "Craft" }, }, current_company: 15, }, user_context: context, }; const spreadsheetData = { sheets: [ { id: "sheet1", cells: { A1: { content: `=ODOO.PIVOT(1, "probability")` }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], context: { allowed_company_ids: [16], default_stage_id: 9, search_default_stage_id: 90, tz: "nz", lang: "EN", uid: 40, }, }, }, }; const expectedFetchContext = { allowed_company_ids: [15], default_stage_id: 9, search_default_stage_id: 90, tz: "bx", lang: "FR", uid: 4, }; patchWithCleanup(session, testSession); const model = await createModelWithDataSource({ spreadsheetData, mockRPC: function (route, { model, method, kwargs }) { if (model !== "partner") { return; } switch (method) { case "read_group": assert.step("read_group"); assert.deepEqual(kwargs.context, expectedFetchContext, "read_group"); break; } }, }); await waitForDataSourcesLoaded(model); assert.verifySteps(["read_group", "read_group", "read_group", "read_group"]); } ); QUnit.test("Context is purged from PivotView related keys", async function (assert) { const spreadsheetData = { sheets: [ { id: "sheet1", cells: { A1: { content: `=ODOO.PIVOT(1, "probability")` }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["foo"], rowGroupBys: ["bar"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", context: { pivot_measures: ["__count"], // inverse row and col group bys pivot_row_groupby: ["test"], pivot_column_groupby: ["check"], dummyKey: "true", }, }, }, }; const model = await createModelWithDataSource({ spreadsheetData, mockRPC: function (route, { model, method, kwargs }) { if (model === "partner" && method === "read_group") { assert.step(`pop`); assert.notOk( ["pivot_measures", "pivot_row_groupby", "pivot_column_groupby"].some( (val) => val in (kwargs.context || {}) ), "The context should not contain pivot related keys" ); } }, }); await waitForDataSourcesLoaded(model); assert.verifySteps(["pop", "pop", "pop", "pop"]); }); QUnit.test("fetch metadata only once per model", async function (assert) { const spreadsheetData = { sheets: [ { id: "sheet1", cells: { A1: { content: `=ODOO.PIVOT(1, "probability")` }, A2: { content: `=ODOO.PIVOT(2, "probability")` }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], context: {}, }, 2: { id: 2, colGroupBys: ["bar"], domain: [], measures: [{ field: "probability", operator: "max" }], model: "partner", rowGroupBys: ["foo"], context: {}, }, }, }; const model = await createModelWithDataSource({ spreadsheetData, mockRPC: function (route, { model, method, kwargs }) { if (model === "partner" && method === "fields_get") { assert.step(`${model}/${method}`); } else if (model === "ir.model" && method === "search_read") { assert.step(`${model}/${method}`); } }, }); await waitForDataSourcesLoaded(model); assert.verifySteps(["partner/fields_get"]); }); QUnit.test("don't fetch pivot data if no formula use it", async function (assert) { const spreadsheetData = { sheets: [ { id: "sheet1", }, { id: "sheet2", cells: { A1: { content: `=ODOO.PIVOT("1", "probability")` }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], }, }, }; const model = await createModelWithDataSource({ spreadsheetData, mockRPC: function (route, { model, method, kwargs }) { if (!["partner", "ir.model"].includes(model)) { return; } assert.step(`${model}/${method}`); }, }); assert.verifySteps([]); model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: "sheet1", sheetIdTo: "sheet2" }); assert.equal(getCellValue(model, "A1"), "Loading..."); await nextTick(); assert.verifySteps([ "partner/fields_get", "partner/read_group", "partner/read_group", "partner/read_group", "partner/read_group", ]); assert.equal(getCellValue(model, "A1"), 131); }); QUnit.test("evaluates only once when two pivots are loading", async function (assert) { const spreadsheetData = { sheets: [{ id: "sheet1" }], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], }, 2: { id: 2, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], }, }, }; const model = await createModelWithDataSource({ spreadsheetData, }); model.config.dataSources.addEventListener("data-source-updated", () => assert.step("data-source-notified") ); setCellContent(model, "A1", '=ODOO.PIVOT("1", "probability")'); setCellContent(model, "A2", '=ODOO.PIVOT("2", "probability")'); assert.equal(getCellValue(model, "A1"), "Loading..."); assert.equal(getCellValue(model, "A2"), "Loading..."); await nextTick(); assert.equal(getCellValue(model, "A1"), 131); assert.equal(getCellValue(model, "A2"), 131); assert.verifySteps(["data-source-notified"], "evaluation after both pivots are loaded"); }); QUnit.test("concurrently load the same pivot twice", async function (assert) { const spreadsheetData = { sheets: [{ id: "sheet1" }], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: ["bar"], }, }, }; const model = await createModelWithDataSource({ spreadsheetData, }); // the data loads first here, when we insert the first pivot function setCellContent(model, "A1", '=ODOO.PIVOT("1", "probability")'); assert.equal(getCellValue(model, "A1"), "Loading..."); // concurrently reload the same pivot model.dispatch("REFRESH_PIVOT", { id: 1 }); await nextTick(); assert.equal(getCellValue(model, "A1"), 131); }); QUnit.test("display loading while data is not fully available", async function (assert) { const metadataPromise = makeDeferred(); const dataPromise = makeDeferred(); const spreadsheetData = { sheets: [ { id: "sheet1", cells: { A1: { content: `=ODOO.PIVOT.HEADER(1, "measure", "probability")` }, A2: { content: `=ODOO.PIVOT.HEADER(1, "product_id", 37)` }, A3: { content: `=ODOO.PIVOT(1, "probability")` }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["product_id"], domain: [], measures: [{ field: "probability", operator: "avg" }], model: "partner", rowGroupBys: [], }, }, }; const model = await createModelWithDataSource({ spreadsheetData, mockRPC: async function (route, args, performRPC) { const { model, method, kwargs } = args; const result = await performRPC(route, args); if (model === "partner" && method === "fields_get") { assert.step(`${model}/${method}`); await metadataPromise; } if ( model === "partner" && method === "read_group" && kwargs.groupby[0] === "product_id" ) { assert.step(`${model}/${method}`); await dataPromise; } if (model === "product" && method === "name_get") { assert.ok(false, "should not be called because data is put in cache"); } return result; }, }); assert.strictEqual(getCellValue(model, "A1"), "Loading..."); assert.strictEqual(getCellValue(model, "A2"), "Loading..."); assert.strictEqual(getCellValue(model, "A3"), "Loading..."); metadataPromise.resolve(); await nextTick(); setCellContent(model, "A10", "1"); // trigger a new evaluation (might also be caused by other async formulas resolving) assert.strictEqual(getCellValue(model, "A1"), "Loading..."); assert.strictEqual(getCellValue(model, "A2"), "Loading..."); assert.strictEqual(getCellValue(model, "A3"), "Loading..."); dataPromise.resolve(); await nextTick(); setCellContent(model, "A10", "2"); assert.strictEqual(getCellValue(model, "A1"), "Probability"); assert.strictEqual(getCellValue(model, "A2"), "xphone"); assert.strictEqual(getCellValue(model, "A3"), 131); assert.verifySteps(["partner/fields_get", "partner/read_group"]); }); QUnit.test("pivot grouped by char field which represents numbers", async function (assert) { const serverData = getBasicServerData(); serverData.models.partner.records = [ { id: 1, name: "111", probability: 11 }, { id: 2, name: "000111", probability: 15 }, ]; const { model } = await createSpreadsheetWithPivot({ serverData, arch: /*xml*/ ` `, }); const A3 = getCell(model, "A3"); const A4 = getCell(model, "A4"); assert.strictEqual(A3.content, '=ODOO.PIVOT.HEADER(1,"name","000111")'); assert.strictEqual(A4.content, '=ODOO.PIVOT.HEADER(1,"name",111)'); assert.strictEqual(A3.evaluated.value, "000111"); assert.strictEqual(A4.evaluated.value, "111"); const B3 = getCell(model, "B3"); const B4 = getCell(model, "B4"); assert.strictEqual(B3.content, '=ODOO.PIVOT(1,"probability","name","000111")'); assert.strictEqual(B4.content, '=ODOO.PIVOT(1,"probability","name",111)'); assert.strictEqual(B3.evaluated.value, 15); assert.strictEqual(B4.evaluated.value, 11); }); QUnit.test("relational PIVOT.HEADER with missing id", async function (assert) { assert.expect(1); const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); const sheetId = model.getters.getActiveSheetId(); model.dispatch("UPDATE_CELL", { col: 4, row: 9, content: `=ODOO.PIVOT.HEADER("1", "product_id", "1111111")`, sheetId, }); await waitForDataSourcesLoaded(model); assert.equal( getCell(model, "E10").evaluated.error.message, "Unable to fetch the label of 1111111 of model product" ); }); QUnit.test("relational PIVOT.HEADER with undefined id", async function (assert) { assert.expect(2); const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); setCellContent(model, "F10", `=ODOO.PIVOT.HEADER("1", "product_id", A25)`); assert.equal(getCell(model, "A25"), null, "the cell should be empty"); await waitForDataSourcesLoaded(model); assert.equal(getCellValue(model, "F10"), "None"); }); QUnit.test("Verify pivot measures are correctly computed :)", async function (assert) { assert.expect(4); const { model } = await createSpreadsheetWithPivot(); assert.equal(getCellValue(model, "B4"), 11); assert.equal(getCellValue(model, "C3"), 15); assert.equal(getCellValue(model, "D4"), 10); assert.equal(getCellValue(model, "E4"), 95); }); QUnit.test("can import/export sorted pivot", async (assert) => { const spreadsheetData = { pivots: { 1: { id: "1", colGroupBys: ["foo"], domain: [], measures: [{ field: "probability" }], model: "partner", rowGroupBys: ["bar"], sortedColumn: { measure: "probability", order: "asc", groupId: [[], [1]], }, name: "A pivot", context: {}, fieldMatching: {}, }, }, }; const model = await createModelWithDataSource({ spreadsheetData }); assert.deepEqual(model.getters.getPivotDefinition(1).sortedColumn, { measure: "probability", order: "asc", groupId: [[], [1]], }); assert.deepEqual(model.exportData().pivots, spreadsheetData.pivots); }); QUnit.test("Can group by many2many field ", async (assert) => { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); assert.equal(getCellFormula(model, "A3"), '=ODOO.PIVOT.HEADER(1,"tag_ids","false")'); assert.equal(getCellFormula(model, "A4"), '=ODOO.PIVOT.HEADER(1,"tag_ids",42)'); assert.equal(getCellFormula(model, "A5"), '=ODOO.PIVOT.HEADER(1,"tag_ids",67)'); assert.equal( getCellFormula(model, "B3"), '=ODOO.PIVOT(1,"probability","tag_ids","false","foo",1)' ); assert.equal( getCellFormula(model, "B4"), '=ODOO.PIVOT(1,"probability","tag_ids",42,"foo",1)' ); assert.equal( getCellFormula(model, "B5"), '=ODOO.PIVOT(1,"probability","tag_ids",67,"foo",1)' ); assert.equal( getCellFormula(model, "C3"), '=ODOO.PIVOT(1,"probability","tag_ids","false","foo",2)' ); assert.equal( getCellFormula(model, "C4"), '=ODOO.PIVOT(1,"probability","tag_ids",42,"foo",2)' ); assert.equal( getCellFormula(model, "C5"), '=ODOO.PIVOT(1,"probability","tag_ids",67,"foo",2)' ); assert.equal(getCellValue(model, "A3"), "None"); assert.equal(getCellValue(model, "A4"), "isCool"); assert.equal(getCellValue(model, "A5"), "Growing"); assert.equal(getCellValue(model, "B3"), ""); assert.equal(getCellValue(model, "B4"), "11"); assert.equal(getCellValue(model, "B5"), "11"); assert.equal(getCellValue(model, "C3"), ""); assert.equal(getCellValue(model, "C4"), "15"); assert.equal(getCellValue(model, "C5"), ""); }); QUnit.test("PIVOT formulas are correctly formatted at evaluation", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); assert.strictEqual(getCell(model, "B3").evaluated.format, "0"); assert.strictEqual(getCell(model, "C3").evaluated.format, "#,##0.00"); }); QUnit.test( "PIVOT formulas with monetary measure are correctly formatted at evaluation", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); assert.strictEqual(getCell(model, "B3").evaluated.format, "#,##0.00[$€]"); } ); QUnit.test( "PIVOT.HEADER formulas are correctly formatted at evaluation", async function (assert) { const { model } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); assert.strictEqual(getCell(model, "A3").evaluated.format, "#,##0.00"); assert.strictEqual(getCell(model, "B1").evaluated.format, "mm/dd/yyyy"); assert.strictEqual(getCell(model, "B2").evaluated.format, undefined); } ); QUnit.test("can edit pivot domain", async (assert) => { const { model } = await createSpreadsheetWithPivot(); const [pivotId] = model.getters.getPivotIds(); assert.deepEqual(model.getters.getPivotDefinition(pivotId).domain, []); assert.strictEqual(getCellValue(model, "B4"), 11); model.dispatch("UPDATE_ODOO_PIVOT_DOMAIN", { pivotId, domain: [["foo", "in", [55]]], }); assert.deepEqual(model.getters.getPivotDefinition(pivotId).domain, [["foo", "in", [55]]]); await waitForDataSourcesLoaded(model); assert.strictEqual(getCellValue(model, "B4"), ""); model.dispatch("REQUEST_UNDO"); await waitForDataSourcesLoaded(model); assert.deepEqual(model.getters.getPivotDefinition(pivotId).domain, []); await waitForDataSourcesLoaded(model); assert.strictEqual(getCellValue(model, "B4"), 11); model.dispatch("REQUEST_REDO"); assert.deepEqual(model.getters.getPivotDefinition(pivotId).domain, [["foo", "in", [55]]]); await waitForDataSourcesLoaded(model); assert.strictEqual(getCellValue(model, "B4"), ""); }); QUnit.test("edited domain is exported", async (assert) => { const { model } = await createSpreadsheetWithPivot(); const [pivotId] = model.getters.getPivotIds(); model.dispatch("UPDATE_ODOO_PIVOT_DOMAIN", { pivotId, domain: [["foo", "in", [55]]], }); assert.deepEqual(model.exportData().pivots["1"].domain, [["foo", "in", [55]]]); }); QUnit.test("field matching is removed when filter is deleted", async function (assert) { const { model } = await createSpreadsheetWithPivot(); await addGlobalFilter( model, { filter: { id: "42", type: "relation", label: "test", defaultValue: [41], modelName: undefined, rangeType: undefined, }, }, { pivot: { 1: { chain: "product_id", type: "many2one" } }, } ); const [filter] = model.getters.getGlobalFilters(); const matching = { chain: "product_id", type: "many2one", }; assert.deepEqual(model.getters.getPivotFieldMatching("1", filter.id), matching); assert.deepEqual(model.getters.getPivotDataSource("1").getComputedDomain(), [ ["product_id", "in", [41]], ]); model.dispatch("REMOVE_GLOBAL_FILTER", { id: filter.id, }); assert.deepEqual( model.getters.getPivotFieldMatching("1", filter.id), undefined, "it should have removed the pivot and its fieldMatching and datasource altogether" ); assert.deepEqual(model.getters.getPivotDataSource("1").getComputedDomain(), []); model.dispatch("REQUEST_UNDO"); assert.deepEqual(model.getters.getPivotFieldMatching("1", filter.id), matching); assert.deepEqual(model.getters.getPivotDataSource("1").getComputedDomain(), [ ["product_id", "in", [41]], ]); model.dispatch("REQUEST_REDO"); assert.deepEqual(model.getters.getPivotFieldMatching("1", filter.id), undefined); assert.deepEqual(model.getters.getPivotDataSource("1").getComputedDomain(), []); }); QUnit.test( "Load pivot spreadsheet with models that cannot be accessed", async function (assert) { let hasAccessRights = true; const { model } = await createSpreadsheetWithPivot({ mockRPC: async function (route, args) { if ( args.model === "partner" && args.method === "read_group" && !hasAccessRights ) { const error = new RPCError(); error.data = { message: "ya done!" }; throw error; } }, }); const headerCell = getCell(model, "A3"); const cell = getCell(model, "C3"); await waitForDataSourcesLoaded(model); assert.equal(headerCell.evaluated.value, "No"); assert.equal(cell.evaluated.value, 15); hasAccessRights = false; model.dispatch("REFRESH_PIVOT", { id: "1" }); await waitForDataSourcesLoaded(model); assert.equal(headerCell.evaluated.value, "#ERROR"); assert.equal(headerCell.evaluated.error.message, "ya done!"); assert.equal(cell.evaluated.value, "#ERROR"); assert.equal(cell.evaluated.error.message, "ya done!"); } ); });