import { defineSpreadsheetModels, Partner, Product } from "@spreadsheet/../tests/helpers/data"; import { describe, expect, test } from "@odoo/hoot"; import { animationFrame } from "@odoo/hoot-mock"; import * as spreadsheet from "@odoo/o-spreadsheet"; const { toCartesian, toZone } = spreadsheet.helpers; import { autofill, selectCell, setCellContent, setCellFormat, setCellStyle, updatePivot, } from "@spreadsheet/../tests/helpers/commands"; import { getCellFormula, getCell } from "@spreadsheet/../tests/helpers/getters"; import { createSpreadsheetWithPivot } from "@spreadsheet/../tests/helpers/pivot"; import { createModelFromGrid } from "@spreadsheet/../tests/helpers/model"; import { waitForDataLoaded } from "@spreadsheet/helpers/model"; import { allowTranslations } from "@web/../tests/web_test_helpers"; describe.current.tags("headless"); defineSpreadsheetModels(); /** * Get the computed value that would be autofilled starting from the given xc. * The starting xc should contains a Pivot formula */ function getPivotAutofillValue(model, xc, { direction, steps }) { const content = getCellFormula(model, xc); const column = ["left", "right"].includes(direction); const increment = ["left", "top"].includes(direction) ? -steps : steps; return model.getters.getPivotNextAutofillValue(content, column, increment); } test("Autofill pivot values", async function () { const { model } = await createSpreadsheetWithPivot({ pivotType: "static" }); // From value to value expect(getPivotAutofillValue(model, "C3", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "C4") ); expect(getPivotAutofillValue(model, "B4", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "B3") ); expect(getPivotAutofillValue(model, "C3", { direction: "right", steps: 1 })).toBe( getCellFormula(model, "D3") ); expect(getPivotAutofillValue(model, "C3", { direction: "left", steps: 1 })).toBe( getCellFormula(model, "B3") ); expect(getPivotAutofillValue(model, "C3", { direction: "bottom", steps: 2 })).toBe( getCellFormula(model, "C5") ); expect(getPivotAutofillValue(model, "C3", { direction: "bottom", steps: 3 })).toBe(""); expect(getPivotAutofillValue(model, "C3", { direction: "right", steps: 4 })).toBe(""); // From value to header expect(getPivotAutofillValue(model, "B4", { direction: "left", steps: 1 })).toBe( getCellFormula(model, "A4") ); expect(getPivotAutofillValue(model, "B4", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "B3") ); expect(getPivotAutofillValue(model, "B4", { direction: "top", steps: 2 })).toBe( getCellFormula(model, "B2") ); expect(getPivotAutofillValue(model, "B4", { direction: "top", steps: 3 })).toBe( getCellFormula(model, "B1") ); // From header to header expect(getPivotAutofillValue(model, "B3", { direction: "right", steps: 1 })).toBe( getCellFormula(model, "C3") ); expect(getPivotAutofillValue(model, "B3", { direction: "right", steps: 2 })).toBe( getCellFormula(model, "D3") ); expect(getPivotAutofillValue(model, "B3", { direction: "left", steps: 1 })).toBe( getCellFormula(model, "A3") ); expect(getPivotAutofillValue(model, "B1", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "B2") ); expect(getPivotAutofillValue(model, "B3", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "B2") ); expect(getPivotAutofillValue(model, "A4", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "A5") ); expect(getPivotAutofillValue(model, "A4", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "A3") ); expect(getPivotAutofillValue(model, "A4", { direction: "bottom", steps: 2 })).toBe(""); expect(getPivotAutofillValue(model, "A4", { direction: "top", steps: 3 })).toBe(""); // From header to value expect(getPivotAutofillValue(model, "B2", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "B3") ); expect(getPivotAutofillValue(model, "B2", { direction: "bottom", steps: 2 })).toBe( getCellFormula(model, "B4") ); expect(getPivotAutofillValue(model, "B2", { direction: "bottom", steps: 4 })).toBe(""); expect(getPivotAutofillValue(model, "A3", { direction: "right", steps: 1 })).toBe( getCellFormula(model, "B3") ); expect(getPivotAutofillValue(model, "A3", { direction: "right", steps: 5 })).toBe( getCellFormula(model, "F3") ); expect(getPivotAutofillValue(model, "A3", { direction: "right", steps: 6 })).toBe(""); // From total row header to value expect(getPivotAutofillValue(model, "A5", { direction: "right", steps: 1 })).toBe( getCellFormula(model, "B5") ); expect(getPivotAutofillValue(model, "A5", { direction: "right", steps: 5 })).toBe( getCellFormula(model, "F5") ); }); test("Autofill with pivot positions", async function () { const { model } = await createSpreadsheetWithPivot({ pivotType: "static" }); setCellContent(model, "C3", `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",1)`); expect(getPivotAutofillValue(model, "C3", { direction: "left", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",0)` ); /** Would be negative => just copy the value */ expect(getPivotAutofillValue(model, "C3", { direction: "left", steps: 2 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",1)` ); expect(getPivotAutofillValue(model, "C3", { direction: "right", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",2)` ); expect(getPivotAutofillValue(model, "C3", { direction: "right", steps: 10 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",11)` ); expect(getPivotAutofillValue(model, "C3", { direction: "top", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",0,"#foo",1)` ); /** Would be negative => just copy the value */ expect(getPivotAutofillValue(model, "C3", { direction: "top", steps: 2 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",1,"#foo",1)` ); expect(getPivotAutofillValue(model, "C3", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","#bar",2,"#foo",1)` ); expect( getPivotAutofillValue(model, "C3", { direction: "bottom", steps: 10, }) ).toBe(`=PIVOT.VALUE(1,"probability","#bar",11,"#foo",1)`); }); test("Autofill with references works like any regular function (no custom autofill)", async function () { const { model } = await createSpreadsheetWithPivot({ pivotType: "static" }); setCellContent(model, "A1", `=PIVOT.VALUE(1,"probability","bar",B2,"foo",$C$3)`); selectCell(model, "A1"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 1 }); model.dispatch("AUTOFILL"); expect(getCellFormula(model, "A2")).toBe(`=PIVOT.VALUE(1,"probability","bar",B3,"foo",$C$3)`); }); test("Autofill non-odoo pivot should copy the formula", function () { allowTranslations(); // prettier-ignore const grid = { A1: "Customer", B1: "Price", C1: `=PIVOT.VALUE(1, "Price")`, A2: "Alice", B2: "10", A3: "", B3: "20", A4: "Olaf", B4: "30", }; const model = createModelFromGrid(grid); const pivot = { name: "Pivot", type: "SPREADSHEET", dataSet: { zone: toZone("A1:B4"), sheetId: model.getters.getActiveSheetId(), }, rows: [{ fieldName: "Customer", order: "asc" }], columns: [], measures: [{ id: "price", fieldName: "Price", aggregator: "sum" }], }; model.dispatch("ADD_PIVOT", { pivot, pivotId: "1" }); selectCell(model, "C1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 1 }); model.dispatch("AUTOFILL"); expect(getCellFormula(model, "C2")).toBe(`=PIVOT.VALUE(1, "Price")`); }); test("Can autofill positional col headers horizontally", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); setCellContent(model, "B1", `=PIVOT.HEADER(1,"#product_id",1)`); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe( `=PIVOT.HEADER(1,"#product_id",2)` ); selectCell(model, "B1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 0 }); const tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent).toEqual([{ value: "xpad" }]); }); test("Can autofill positional row headers vertically", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); setCellContent(model, "A3", `=PIVOT.HEADER(1,"date:month",DATE(2016,4,1),"#product_id",1)`); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.HEADER(1,"date:month",DATE(2016,4,1),"#product_id",2)` ); selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 3 }); const tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent).toEqual([{ value: "April 2016" }, { value: "" }]); }); test("Can autofill positional row headers horizontally", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); setCellContent(model, "A3", `=PIVOT.HEADER(1,"#product_id",1)`); expect(getPivotAutofillValue(model, "A3", { direction: "right", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability:avg","date:month",DATE(2016,4,1))` ); selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 3 }); const tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent).toEqual([{ value: "xpad" }]); }); test("Can autofill positional col horizontally", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); setCellContent(model, "B1", `=PIVOT.VALUE(1,"probability","#product_id",1)`); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","#product_id",2)` ); selectCell(model, "B1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 0 }); const tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent).toEqual([{ value: "xpad" }]); }); test("Can autofill positional row vertically", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); setCellContent( model, "A3", `=PIVOT.VALUE(1,"probability","date:month",DATE(2016,4,1),"#product_id",1)` ); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability","date:month",DATE(2016,4,1),"#product_id",2)` ); selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 3 }); const tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent).toEqual([{ value: "April 2016" }, { value: "" }]); }); test("Autofill last column cells vertically by targeting col headers", async function () { const { model } = await createSpreadsheetWithPivot({ pivotType: "static" }); expect(getPivotAutofillValue(model, "F3", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "F2") ); expect(getPivotAutofillValue(model, "F3", { direction: "top", steps: 2 })).toBe( getCellFormula(model, "F1") ); expect(getPivotAutofillValue(model, "F5", { direction: "top", steps: 3 })).toBe( getCellFormula(model, "F2") ); expect(getPivotAutofillValue(model, "F5", { direction: "top", steps: 4 })).toBe( getCellFormula(model, "F1") ); setCellContent(model, "H10", `=PIVOT.VALUE(1,"probability:avg","bar","true")`); expect(getPivotAutofillValue(model, "H10", { direction: "top", steps: 5 })).toBe(""); }); test("Autofill pivot values with date in rows", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "A4").replace("DATE(2016,10,1)", "DATE(2016,5,1)") ); expect(getPivotAutofillValue(model, "A5", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:month",DATE(2017,1,1))' ); expect(getPivotAutofillValue(model, "B3", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "B4").replace("DATE(2016,10,1)", "DATE(2016,5,1)") ); expect(getPivotAutofillValue(model, "B5", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "B5").replace("DATE(2016,12,1)", "DATE(2017,1,1)") ); expect(getPivotAutofillValue(model, "B5", { direction: "top", steps: 1 })).toBe( getCellFormula(model, "B4").replace("DATE(2016,10,1)", "DATE(2016,11,1)") ); expect(getPivotAutofillValue(model, "F6", { direction: "top", steps: 1 })).toBe(""); }); test("Autofill pivot values with date in cols", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getCellFormula(model, "B1")).toBe('=PIVOT.HEADER(1,"date:day",DATE(2016,4,14))'); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:day",DATE(2016,4,15))' ); expect(getCellFormula(model, "B2")).toBe( '=PIVOT.HEADER(1,"date:day",DATE(2016,4,14),"measure","probability:avg")' ); expect(getPivotAutofillValue(model, "B2", { direction: "right", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:day",DATE(2016,4,15),"measure","probability:avg")' ); expect(getCellFormula(model, "B3")).toBe( '=PIVOT.VALUE(1,"probability:avg","foo",1,"date:day",DATE(2016,4,14))' ); expect(getPivotAutofillValue(model, "B3", { direction: "right", steps: 1 })).toBe( '=PIVOT.VALUE(1,"probability:avg","foo",1,"date:day",DATE(2016,4,15))' ); setCellContent(model, "C1", '=PIVOT.HEADER(1,"date:day",DATE(2016,4,15))'); expect(getPivotAutofillValue(model, "C1", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:day",DATE(2016,4,15),"measure","probability:avg")' ); expect(getPivotAutofillValue(model, "C1", { direction: "bottom", steps: 2 })).toBe( '=PIVOT.VALUE(1,"probability:avg","foo",1,"date:day",DATE(2016,4,15))' ); }); test("Autofill pivot values with date in cols and multiple cols", async (assert) => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); setCellContent(model, "H2", '=PIVOT.HEADER(1,"date:month","05/2016")'); expect(getPivotAutofillValue(model, "H2", { direction: "bottom", steps: 1 })).toBe(""); expect(getPivotAutofillValue(model, "H2", { direction: "bottom", steps: 2 })).toBe(""); expect(getPivotAutofillValue(model, "H2", { direction: "bottom", steps: 3 })).toBe(""); }); test("Autofill pivot values with date (day)", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getCellFormula(model, "A3")).toBe('=PIVOT.HEADER(1,"date:day",DATE(2016,4,14))'); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "14 Apr 2016" }, ]); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:day",DATE(2016,4,15))' ); }); test("Autofill pivot values with date (week) 2020 has 53 weeks", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); setCellContent(model, "A1", '=PIVOT.HEADER(1,"date:week","52/2020")'); expect(getPivotAutofillValue(model, "A1", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.HEADER(1,"date:week","53/2020")' ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A1"))).toEqual([ { value: "W52 2020" }, ]); }); test("Autofill empty pivot date value", async function () { const { model, pivotId } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, pivotType: "static", }); for (const granularity of [ "day", "week", "month", "quarter", "year", "quarter_number", "month_number", "iso_week_number", "day_of_month", ]) { updatePivot(model, pivotId, { rows: [{ fieldName: "date", granularity, order: "asc" }], }); await animationFrame(); setCellContent(model, "A1", `=PIVOT.HEADER(1,"date:${granularity}",FALSE)`); expect(getPivotAutofillValue(model, "A1", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.HEADER(1,"date:${granularity}",FALSE)` ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A1"))).toEqual([ { value: "None" }, ]); } }); test("Autofill past bound pivot date value", async function () { const { model, pivotId } = await createSpreadsheetWithPivot({ arch: /* xml */ ` `, }); const granularities = [ ["quarter_number", 1, 4], ["month_number", 1, 12], ["iso_week_number", 0, 54], ["day_of_month", 1, 31], ]; const steps = [1, 10, 20, 30, 40, 50, 60, 70, 80]; for (const grIdx in granularities) { const [granularity, lowerBound, upperBound] = granularities[grIdx]; updatePivot(model, pivotId, { rows: [{ fieldName: "date", granularity, order: "asc" }], }); await animationFrame(); setCellContent(model, "A1", `=PIVOT.HEADER(1,"date:${granularity}",1)`); for (const stepIdx in steps) { const step = steps[stepIdx]; const expectedValue = 1 + step; const expectedFormula = lowerBound <= expectedValue && expectedValue <= upperBound ? `=PIVOT.HEADER(1,"date:${granularity}",${expectedValue})` : ""; expect(getPivotAutofillValue(model, "A1", { direction: "bottom", steps: step })).toBe( expectedFormula ); } } }); test("Autofill pivot values with date (month)", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.HEADER(1,"date:month",DATE(2016,5,1))` ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "April 2016" }, ]); }); test("Autofill pivot values with date (quarter)", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "A3").replace("2/2016", "3/2016") ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "Q2 2016" }, ]); }); test("Autofill pivot values with date (year)", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( getCellFormula(model, "A3").replace("2016", "2017") ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "2016" }, ]); }); test("Autofill pivot values with date (no defined interval)", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(getPivotAutofillValue(model, "A3", { direction: "bottom", steps: 1 })).toBe( `=PIVOT.HEADER(1,"date:month",DATE(2016,5,1))` ); }); test("Tooltip of pivot formulas", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "B3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "E3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "F3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "B1"))).toEqual([{ value: "1" }]); expect(model.getters.getTooltipFormula(getCellFormula(model, "B2"))).toEqual([ { value: "1" }, { value: "Probability" }, ]); expect(model.getters.getTooltipFormula(`=PIVOT.HEADER("1")`, true)).toEqual([ { value: "Total" }, ]); expect(model.getters.getTooltipFormula(`=PIVOT.HEADER("1")`, false)).toEqual([ { value: "Total" }, ]); }); test("Tooltip of pivot formulas with 2 measures", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); expect(model.getters.getTooltipFormula(getCellFormula(model, "A3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "B3"))).toEqual([ { value: "2016" }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "C3"), true)).toEqual([ { value: "Raoul" }, { value: "Foo" }, ]); }); test("Tooltip of empty pivot formula is empty", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 15, row: 15 }); expect(model.getters.getAutofillTooltip()).toBe(undefined); }); test("Autofill content which contains pivots but which is not a pivot", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); const a3 = getCellFormula(model, "A3").replace("=", ""); const content = `=${a3} + ${a3}`; setCellContent(model, "F6", content); expect(getPivotAutofillValue(model, "F6", { direction: "bottom", steps: 1 })).toBe(content); expect(getPivotAutofillValue(model, "F6", { direction: "right", steps: 1 })).toBe(content); }); test("Autofill pivot formula with missing pivotId", async function () { allowTranslations(); const model = new spreadsheet.Model({ sheets: [ { colNumber: 1, rowNumber: 2, cells: { A1: '=PIVOT.VALUE("1","bar","date","05/2023")', B1: '=PIVOT.HEADER("1","date","05/2023")', }, }, ], }); expect(getPivotAutofillValue(model, "A1", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.VALUE("1","bar","date","05/2023")' ); expect(getPivotAutofillValue(model, "B1", { direction: "bottom", steps: 1 })).toBe( '=PIVOT.HEADER("1","date","05/2023")' ); expect(model.getters.getTooltipFormula(getCellFormula(model, "A1"), false)).toEqual([ { title: "Missing pivot", value: "Missing pivot #1", }, ]); expect(model.getters.getTooltipFormula(getCellFormula(model, "B1"), false)).toEqual([ { title: "Missing pivot", value: "Missing pivot #1", }, ]); }); test("Can autofill col headers horizontally", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); const sortedTagNames = ["isCool", "Growing", "None"]; selectCell(model, "B1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 0 }); let tooltipContent; tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[1]); selectCell(model, "C1"); model.dispatch("AUTOFILL_SELECT", { col: 3, row: 0 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[2]); selectCell(model, "D1"); model.dispatch("AUTOFILL_SELECT", { col: 4, row: 0 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Total"); selectCell(model, "E1"); model.dispatch("AUTOFILL_SELECT", { col: 5, row: 0 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[0]); }); test("Can autofill col headers vertically", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); selectCell(model, "B1"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 1 }); let tooltipContent; tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Probability"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 2 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("2016"); selectCell(model, "C1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 1 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Probability"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 2 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("2016"); selectCell(model, "D1"); model.dispatch("AUTOFILL_SELECT", { col: 3, row: 1 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Probability"); model.dispatch("AUTOFILL_SELECT", { col: 3, row: 2 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("2016"); selectCell(model, "E1"); model.dispatch("AUTOFILL_SELECT", { col: 4, row: 1 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Probability"); model.dispatch("AUTOFILL_SELECT", { col: 4, row: 2 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("2016"); model.dispatch("AUTOFILL_SELECT", { col: 4, row: 3 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Total"); }); test("Can autofill row headers horizontally", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); const sortedTagNames = ["isCool", "Growing", "None"]; let tooltipContent; selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 2 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[0]); selectCell(model, "A4"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 3 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[1]); selectCell(model, "A5"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 4 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[0]); selectCell(model, "A6"); model.dispatch("AUTOFILL_SELECT", { col: 3, row: 5 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[2]); selectCell(model, "A7"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 6 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[0]); selectCell(model, "A8"); model.dispatch("AUTOFILL_SELECT", { col: 4, row: 7 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Total"); selectCell(model, "A9"); model.dispatch("AUTOFILL_SELECT", { col: 1, row: 8 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe(sortedTagNames[0]); }); test("Can autofill row headers vertically", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); let tooltipContent; selectCell(model, "A3"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 3 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("xphone"); selectCell(model, "A4"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 4 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("October 2016"); selectCell(model, "A5"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 5 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("xpad"); selectCell(model, "A6"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 6 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("December 2016"); selectCell(model, "A7"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 7 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("xpad"); selectCell(model, "A8"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 8 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Total"); selectCell(model, "A9"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 9 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("Probability"); }); test("Can autofill with month string parent group header and value", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); let tooltipContent; setCellContent(model, "A1", '=PIVOT.HEADER(1,"date:month","04/2016","product_id",37)'); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 1 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("October 2016"); model.dispatch("AUTOFILL"); expect(getCell(model, "A2").content).toBe('=PIVOT.HEADER(1,"date:month",DATE(2016,10,1))'); setCellContent( model, "A1", '=PIVOT.VALUE(1,"probability:avg","date:month","04/2016","product_id",37)' ); selectCell(model, "A1"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 1 }); tooltipContent = model.getters.getAutofillTooltip().props.content; expect(tooltipContent[tooltipContent.length - 1].value).toBe("October 2016"); model.dispatch("AUTOFILL"); expect(getCell(model, "A2").content).toBe( '=PIVOT.VALUE(1,"probability:avg","date:month",DATE(2016,10,1))' ); }); test("Cannot autofill with invalid parent group header and value", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); setCellContent(model, "A1", '=PIVOT.HEADER(1,"date:month",TRUE,"product_id",37)'); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 1 }); expect(model.getters.getAutofillTooltip()).toBe(undefined); model.dispatch("AUTOFILL"); expect(getCell(model, "A2")).toBe(undefined); setCellContent( model, "A1", '=PIVOT.VALUE(1,"probability:avg","date:month",TRUE,"product_id",37)' ); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 1 }); expect(model.getters.getAutofillTooltip()).toBe(undefined); model.dispatch("AUTOFILL"); expect(getCell(model, "A2")).toBe(undefined); }); test("Autofill pivot keeps format but neither style nor border", async function () { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); // Change the format, style and borders of E3 const sheetId = model.getters.getActiveSheetId(); const { col, row } = toCartesian("E3"); const border = { left: { style: "thin", color: "#000" }, }; const style = { textColor: "orange" }; setCellStyle(model, "E3", style); setCellFormat(model, "E3", "#,##0.0"); model.dispatch("SET_BORDER", { sheetId, col, row, border }); // Change the format of E4 setCellFormat(model, "E4", "#,##0.000"); // Check that the format, style and border of E3 have been correctly applied autofill(model, "E3", "E4"); const startingCell = getCell(model, "E3"); expect(startingCell.style).toEqual(style); expect(model.getters.getCellBorder({ sheetId, col, row }).left).toEqual(border.left); expect(startingCell.format).toBe("#,##0.0"); // Check that the format of E3 has been correctly applied to E4 but not the style nor the border const filledCell = getCell(model, "E4"); expect(filledCell.style).toBe(undefined); expect(model.getters.getCellBorder({ sheetId, col, row: row + 1 })).toBe(null); expect(filledCell.format).toBe("#,##0.0"); }); test("Can autofill pivot horizontally with column grouped by date", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, pivotType: "static", }); // Headers setCellContent( model, "B1", `=PIVOT.HEADER(1,"date:month",DATE(2016,4,1),"measure","probability:avg")` ); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe( `=PIVOT.HEADER(1,"date:month",DATE(2016,5,1),"measure","probability:avg")` ); setCellContent(model, "B1", `=PIVOT.HEADER(1,"measure","probability:avg")`); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe(""); // Values setCellContent( model, "B1", `=PIVOT.VALUE(1,"probability:avg","product_id",47,"date:month",DATE(2016,4,1))` ); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe( `=PIVOT.VALUE(1,"probability:avg","product_id",47,"date:month",DATE(2016,5,1))` ); setCellContent(model, "B1", `=PIVOT.VALUE(1,"probability:avg","product_id",47)`); expect(getPivotAutofillValue(model, "B1", { direction: "right", steps: 1 })).toBe(""); }); test("Can autofill pivot with collapsed dimensions", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); const pivotId = model.getters.getPivotIds()[0]; updatePivot(model, pivotId, { collapsedDomains: { ROW: [[{ field: "date:year", value: 2016, type: "date" }]], COL: [], }, }); setCellContent(model, "A1", `=PIVOT.HEADER(1,"date:year",2016)`); selectCell(model, "A1"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 4 }); model.dispatch("AUTOFILL"); expect(getCell(model, "A1").content).toBe(`=PIVOT.HEADER(1,"date:year",2016)`); expect(getCell(model, "A2").content).toBe( `=PIVOT.HEADER(1,"date:year",2016,"date:month",DATE(2016,4,1))` ); expect(getCell(model, "A3").content).toBe( `=PIVOT.HEADER(1,"date:year",2016,"date:month",DATE(2016,10,1))` ); expect(getCell(model, "A4").content).toBe( `=PIVOT.HEADER(1,"date:year",2016,"date:month",DATE(2016,12,1))` ); expect(getCell(model, "A5").content).toBe(`=PIVOT.HEADER(1)`); }); test("Autofill does not crash when autofilling wrong relational id", async () => { const { model } = await createSpreadsheetWithPivot({ arch: /*xml*/ ` `, }); const formula = `=PIVOT.HEADER(1,"product_id",9999)`; expect(model.getters.getTooltipFormula(formula)).toEqual([{ value: "Unknown" }]); }); test("Can autofill pivot with custom groups", async () => { Product._records.push( { id: 200, display_name: "chair", name: "chair" }, { id: 201, display_name: "table", name: "table" } ); Partner._records.push( { id: 200, product_id: 200, probability: 100, bar: true }, { id: 201, product_id: 201, probability: 50, bar: false }, { id: 202, product_id: false, probability: 10, bar: true } ); const { model } = await createSpreadsheetWithPivot(); const pivotId = model.getters.getPivotIds()[0]; updatePivot(model, pivotId, { columns: [{ fieldName: "GroupedProducts", order: "asc" }], rows: [{ fieldName: "bar" }], measures: [{ id: "probability:sum", fieldName: "probability", aggregator: "sum" }], customFields: { GroupedProducts: { parentField: "product_id", name: "GroupedProducts", groups: [{ name: "xphone,xpad", values: [37, 41] }], }, }, }); await waitForDataLoaded(model); // Autofill headers setCellContent(model, "E1", `=PIVOT.HEADER(1,"GroupedProducts","xphone,xpad")`); selectCell(model, "E1"); model.dispatch("AUTOFILL_SELECT", { col: 5, row: 0 }); model.dispatch("AUTOFILL"); expect(getCell(model, "F1").content).toBe(`=PIVOT.HEADER(1,"GroupedProducts",false)`); selectCell(model, "E1"); model.dispatch("AUTOFILL_SELECT", { col: 2, row: 0 }); model.dispatch("AUTOFILL"); expect(getCell(model, "D1").content).toBe(`=PIVOT.HEADER(1,"GroupedProducts",201)`); expect(getCell(model, "C1").content).toBe(`=PIVOT.HEADER(1,"GroupedProducts",200)`); // Autofill values setCellContent(model, "A1", `=PIVOT.HEADER(1,"GroupedProducts","xphone,xpad")`); selectCell(model, "A1"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 4 }); model.dispatch("AUTOFILL"); expect(getCell(model, "A2").content).toBe( `=PIVOT.HEADER(1,"GroupedProducts","xphone,xpad","measure","probability:sum")` ); expect(getCell(model, "A3").content).toBe( `=PIVOT.VALUE(1,"probability:sum","bar",FALSE,"GroupedProducts","xphone,xpad")` ); expect(getCell(model, "A4").content).toBe( `=PIVOT.VALUE(1,"probability:sum","bar",TRUE,"GroupedProducts","xphone,xpad")` ); expect(getCell(model, "A5").content).toBe( `=PIVOT.VALUE(1,"probability:sum","GroupedProducts","xphone,xpad")` ); // Autofill from "None" header setCellContent(model, "A1", `=PIVOT.HEADER(1, "GroupedProducts",false)`); selectCell(model, "A1"); model.dispatch("AUTOFILL_SELECT", { col: 0, row: 4 }); model.dispatch("AUTOFILL"); expect(getCell(model, "A3").content).toBe( `=PIVOT.VALUE(1,"probability:sum","bar",FALSE,"GroupedProducts",false)` ); expect(getCell(model, "A4").content).toBe( `=PIVOT.VALUE(1,"probability:sum","bar",TRUE,"GroupedProducts",false)` ); });