1084 lines
39 KiB
JavaScript
1084 lines
39 KiB
JavaScript
import { describe, expect, test } from "@odoo/hoot";
|
|
import { makeServerError, mockService, serverState } from "@web/../tests/web_test_helpers";
|
|
import { user } from "@web/core/user";
|
|
|
|
import {
|
|
addGlobalFilter,
|
|
redo,
|
|
selectCell,
|
|
setCellContent,
|
|
undo,
|
|
} from "@spreadsheet/../tests/helpers/commands";
|
|
import {
|
|
getCell,
|
|
getCellContent,
|
|
getCellFormula,
|
|
getCells,
|
|
getCellValue,
|
|
getEvaluatedCell,
|
|
getEvaluatedGrid,
|
|
} from "@spreadsheet/../tests/helpers/getters";
|
|
import { THIS_YEAR_GLOBAL_FILTER } from "@spreadsheet/../tests/helpers/global_filter";
|
|
import { createSpreadsheetWithList } from "@spreadsheet/../tests/helpers/list";
|
|
import { createModelWithDataSource } from "@spreadsheet/../tests/helpers/model";
|
|
import { CommandResult } from "@spreadsheet/o_spreadsheet/cancelled_reason";
|
|
|
|
import { animationFrame } from "@odoo/hoot-mock";
|
|
import * as spreadsheet from "@odoo/o-spreadsheet";
|
|
import {
|
|
defineSpreadsheetActions,
|
|
defineSpreadsheetModels,
|
|
generateListDefinition,
|
|
Partner,
|
|
Product,
|
|
} from "@spreadsheet/../tests/helpers/data";
|
|
|
|
import { waitForDataLoaded } from "@spreadsheet/helpers/model";
|
|
const { DEFAULT_LOCALE, PIVOT_TABLE_CONFIG } = spreadsheet.constants;
|
|
const { toZone } = spreadsheet.helpers;
|
|
const { cellMenuRegistry } = spreadsheet.registries;
|
|
|
|
describe.current.tags("headless");
|
|
defineSpreadsheetModels();
|
|
defineSpreadsheetActions();
|
|
|
|
test("List export", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const total = 4 + 10 * 4; // 4 Headers + 10 lines
|
|
expect(Object.values(getCells(model)).length).toBe(total);
|
|
expect(getCellFormula(model, "A1")).toBe(`=ODOO.LIST.HEADER(1,"foo")`);
|
|
expect(getCellFormula(model, "B1")).toBe(`=ODOO.LIST.HEADER(1,"bar")`);
|
|
expect(getCellFormula(model, "C1")).toBe(`=ODOO.LIST.HEADER(1,"date")`);
|
|
expect(getCellFormula(model, "D1")).toBe(`=ODOO.LIST.HEADER(1,"product_id")`);
|
|
expect(getCellFormula(model, "A2")).toBe(`=ODOO.LIST(1,1,"foo")`);
|
|
expect(getCellFormula(model, "B2")).toBe(`=ODOO.LIST(1,1,"bar")`);
|
|
expect(getCellFormula(model, "C2")).toBe(`=ODOO.LIST(1,1,"date")`);
|
|
expect(getCellFormula(model, "D2")).toBe(`=ODOO.LIST(1,1,"product_id")`);
|
|
expect(getCellFormula(model, "A3")).toBe(`=ODOO.LIST(1,2,"foo")`);
|
|
expect(getCellFormula(model, "A11")).toBe(`=ODOO.LIST(1,10,"foo")`);
|
|
expect(getCellFormula(model, "A12")).toBe("");
|
|
});
|
|
|
|
test("Return display name of selection field", async () => {
|
|
const { model } = await createSpreadsheetWithList({
|
|
model: "res.currency",
|
|
columns: ["position"],
|
|
});
|
|
expect(getCellValue(model, "A2")).toBe("A");
|
|
});
|
|
|
|
test("Return display_name of many2one field", async () => {
|
|
const { model } = await createSpreadsheetWithList({ columns: ["product_id"] });
|
|
expect(getCellValue(model, "A2")).toBe("xphone");
|
|
});
|
|
|
|
test("Boolean fields are correctly formatted", async () => {
|
|
const { model } = await createSpreadsheetWithList({ columns: ["bar"] });
|
|
expect(getCellValue(model, "A2")).toBe(true);
|
|
expect(getCellValue(model, "A5")).toBe(false);
|
|
});
|
|
|
|
test("properties field displays property display names", async () => {
|
|
Product._records = [
|
|
{
|
|
id: 1,
|
|
properties_definitions: [
|
|
{ name: "dbfc66e0afaa6a8d", type: "date", string: "prop 1" },
|
|
{ name: "f80b6fb58d0d4c72", type: "integer", string: "prop 2" },
|
|
],
|
|
},
|
|
];
|
|
Partner._records = [
|
|
{
|
|
product_id: 1,
|
|
partner_properties: {
|
|
dbfc66e0afaa6a8d: false,
|
|
f80b6fb58d0d4c72: 0,
|
|
},
|
|
},
|
|
];
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["partner_properties"],
|
|
});
|
|
expect(getCellValue(model, "A2")).toBe("prop 1, prop 2");
|
|
});
|
|
|
|
test("Can display a field which is not in the columns", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
setCellContent(model, "A1", `=ODOO.LIST(1,1,"active")`);
|
|
expect(getCellValue(model, "A1")).toBe("Loading...");
|
|
await waitForDataLoaded(model); // Await for batching collection of missing fields
|
|
await animationFrame();
|
|
expect(getCellValue(model, "A1")).toBe(true);
|
|
});
|
|
|
|
test("Can remove a list with undo after editing a cell", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
expect(getCellContent(model, "B1").startsWith("=ODOO.LIST.HEADER")).toBe(true);
|
|
setCellContent(model, "G10", "should be undoable");
|
|
model.dispatch("REQUEST_UNDO");
|
|
expect(getCellContent(model, "G10")).toBe("");
|
|
model.dispatch("REQUEST_UNDO");
|
|
expect(getCellContent(model, "B1")).toBe("");
|
|
expect(model.getters.getListIds().length).toBe(0);
|
|
});
|
|
|
|
test("List formulas are correctly formatted at evaluation", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: [
|
|
"foo",
|
|
"probability",
|
|
"bar",
|
|
"date",
|
|
"create_date",
|
|
"product_id",
|
|
"pognon",
|
|
"name",
|
|
],
|
|
linesNumber: 2,
|
|
});
|
|
await waitForDataLoaded(model);
|
|
expect(getCell(model, "A2").format).toBe(undefined);
|
|
expect(getCell(model, "B2").format).toBe(undefined);
|
|
expect(getCell(model, "C2").format).toBe(undefined);
|
|
expect(getCell(model, "D2").format).toBe(undefined);
|
|
expect(getCell(model, "E2").format).toBe(undefined);
|
|
expect(getCell(model, "F2").format).toBe(undefined);
|
|
expect(getCell(model, "G2").format).toBe(undefined);
|
|
expect(getCell(model, "G3").format).toBe(undefined);
|
|
expect(getCell(model, "H2").format).toBe(undefined);
|
|
|
|
expect(getEvaluatedCell(model, "A2").format).toBe("0");
|
|
expect(getEvaluatedCell(model, "B2").format).toBe("#,##0.00");
|
|
expect(getEvaluatedCell(model, "C2").format).toBe(undefined);
|
|
expect(getEvaluatedCell(model, "D2").format).toBe("m/d/yyyy");
|
|
expect(getEvaluatedCell(model, "E2").format).toBe("m/d/yyyy hh:mm:ss a");
|
|
expect(getEvaluatedCell(model, "F2").format).toBe(undefined);
|
|
expect(getEvaluatedCell(model, "G2").format).toBe("#,##0.00[$€]");
|
|
expect(getEvaluatedCell(model, "G3").format).toBe("[$$]#,##0.00");
|
|
expect(getEvaluatedCell(model, "H2").format).toBe("@");
|
|
});
|
|
|
|
test("List formulas date formats are locale dependant", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["date", "create_date"],
|
|
linesNumber: 2,
|
|
});
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedCell(model, "A2").format).toBe("m/d/yyyy");
|
|
expect(getEvaluatedCell(model, "B2").format).toBe("m/d/yyyy hh:mm:ss a");
|
|
|
|
const myLocale = { ...DEFAULT_LOCALE, dateFormat: "d/m/yyyy", timeFormat: "hh:mm:ss" };
|
|
model.dispatch("UPDATE_LOCALE", { locale: myLocale });
|
|
|
|
expect(getEvaluatedCell(model, "A2").format).toBe("d/m/yyyy");
|
|
expect(getEvaluatedCell(model, "B2").format).toBe("d/m/yyyy hh:mm:ss");
|
|
});
|
|
|
|
test("Json fields are not supported in list formulas", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["foo", "jsonField"],
|
|
linesNumber: 2,
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.LIST(1,1,"foo")`);
|
|
setCellContent(model, "A2", `=ODOO.LIST(1,1,"jsonField")`);
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedCell(model, "A1").value).toBe(12);
|
|
expect(getEvaluatedCell(model, "A2").value).toBe("#ERROR");
|
|
expect(getEvaluatedCell(model, "A2").message).toBe(`Fields of type "json" are not supported`);
|
|
});
|
|
|
|
test("can get a listId from cell formula", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const listId = model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 });
|
|
expect(listId).toBe("1");
|
|
});
|
|
|
|
test("can get a listId from cell formula with '-' before the formula", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
setCellContent(model, "A1", `=-ODOO.LIST("1","1","foo")`);
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const listId = model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 });
|
|
expect(listId).toBe("1");
|
|
});
|
|
test("can get a listId from cell formula with other numerical values", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
setCellContent(model, "A1", `=3*ODOO.LIST("1","1","foo")`);
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const listId = model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 });
|
|
expect(listId).toBe("1");
|
|
});
|
|
|
|
test("can get a listId from a vectorized cell formula", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
setCellContent(model, "G1", '=LIST(1,SEQUENCE(10),"foo")');
|
|
expect(model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 })).toBe("1");
|
|
expect(model.getters.getListIdFromPosition({ sheetId, col: 0, row: 5 })).toBe("1");
|
|
});
|
|
|
|
test("List datasource is loaded with correct linesNumber", async function () {
|
|
const { model } = await createSpreadsheetWithList({ linesNumber: 2 });
|
|
const [listId] = model.getters.getListIds();
|
|
const dataSource = model.getters.getListDataSource(listId);
|
|
expect(dataSource.maxPosition).toBe(2);
|
|
});
|
|
|
|
test("can get a listId from cell formula within a formula", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
setCellContent(model, "A1", `=SUM(ODOO.LIST("1","1","foo"),1)`);
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const listId = model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 });
|
|
expect(listId).toBe("1");
|
|
});
|
|
|
|
test("can get a listId from cell formula where the id is a reference", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
setCellContent(model, "A1", `=ODOO.LIST(G10,"1","foo")`);
|
|
setCellContent(model, "G10", "1");
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const listId = model.getters.getListIdFromPosition({ sheetId, col: 0, row: 0 });
|
|
expect(listId).toBe("1");
|
|
});
|
|
|
|
test("Referencing non-existing fields does not crash", async function () {
|
|
const forbiddenFieldName = "a_field";
|
|
let spreadsheetLoaded = false;
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["bar", "product_id"],
|
|
mockRPC: async function (route, args) {
|
|
if (
|
|
spreadsheetLoaded &&
|
|
args.method === "web_search_read" &&
|
|
args.model === "partner" &&
|
|
args.kwargs.specification[forbiddenFieldName]
|
|
) {
|
|
// We should not go through this condition if the forbidden fields is properly filtered
|
|
expect(false).toBe(true, {
|
|
message: `${forbiddenFieldName} should have been ignored`,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
const listId = model.getters.getListIds()[0];
|
|
spreadsheetLoaded = true;
|
|
model.dispatch("REFRESH_ALL_DATA_SOURCES");
|
|
await animationFrame();
|
|
setCellContent(model, "A1", `=ODOO.LIST.HEADER("1", "${forbiddenFieldName}")`);
|
|
setCellContent(model, "A2", `=ODOO.LIST("1","1","${forbiddenFieldName}")`);
|
|
|
|
expect(model.getters.getListDataSource(listId).getFields()[forbiddenFieldName]).toBe(undefined);
|
|
expect(getCellValue(model, "A1")).toBe(forbiddenFieldName);
|
|
const A2 = getEvaluatedCell(model, "A2");
|
|
expect(A2.type).toBe("error");
|
|
expect(A2.message).toBe(
|
|
`The field ${forbiddenFieldName} does not exist or you do not have access to that field`
|
|
);
|
|
});
|
|
|
|
test("don't fetch list data if no formula use it", async function () {
|
|
const spreadsheetData = {
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["foo", "contact_name"],
|
|
domain: [],
|
|
model: "partner",
|
|
orderBy: [],
|
|
context: {},
|
|
},
|
|
},
|
|
};
|
|
const model = await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, { model, method }) {
|
|
if (!["partner", "ir.model"].includes(model)) {
|
|
return;
|
|
}
|
|
expect.step(`${model}/${method}`);
|
|
},
|
|
});
|
|
expect.verifySteps([]);
|
|
|
|
setCellContent(model, "A1", `=ODOO.LIST("1", "1", "foo")`);
|
|
/*
|
|
* Ask a first time the value => It will trigger a loading of the data source.
|
|
*/
|
|
expect(getCellValue(model, "A1")).toBe("Loading...");
|
|
await animationFrame();
|
|
expect(getCellValue(model, "A1")).toBe(12);
|
|
expect.verifySteps(["partner/fields_get", "partner/web_search_read"]);
|
|
});
|
|
|
|
test("user context is combined with list context to fetch data", async function () {
|
|
serverState.companies = [
|
|
{ id: 15, name: "Hermit" },
|
|
{ id: 16, name: "Craft" },
|
|
];
|
|
serverState.timezone = "bx";
|
|
serverState.lang = "FR";
|
|
serverState.userContext.allowed_company_ids = [15];
|
|
|
|
const spreadsheetData = {
|
|
sheets: [
|
|
{
|
|
id: "sheet1",
|
|
cells: {
|
|
A1: { content: `=ODOO.LIST("1", "1", "name")` },
|
|
},
|
|
},
|
|
],
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["name", "contact_name"],
|
|
domain: [],
|
|
model: "partner",
|
|
orderBy: [],
|
|
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: serverState.userId,
|
|
};
|
|
const model = await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, { model, method, kwargs }) {
|
|
if (model !== "partner") {
|
|
return;
|
|
}
|
|
switch (method) {
|
|
case "web_search_read":
|
|
expect.step("web_search_read");
|
|
expect(kwargs.context).toEqual(expectedFetchContext);
|
|
break;
|
|
}
|
|
},
|
|
});
|
|
await waitForDataLoaded(model);
|
|
expect.verifySteps(["web_search_read"]);
|
|
});
|
|
|
|
test("rename list with empty name is refused", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const result = model.dispatch("RENAME_ODOO_LIST", {
|
|
listId: "1",
|
|
name: "",
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.EmptyName]);
|
|
});
|
|
|
|
test("rename list with incorrect id is refused", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const result = model.dispatch("RENAME_ODOO_LIST", {
|
|
listId: "invalid",
|
|
name: "name",
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.ListIdNotFound]);
|
|
});
|
|
|
|
test("Undo/Redo for RENAME_ODOO_LIST", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
expect(model.getters.getListName("1")).toBe("List");
|
|
model.dispatch("RENAME_ODOO_LIST", { listId: "1", name: "test" });
|
|
expect(model.getters.getListName("1")).toBe("test");
|
|
model.dispatch("REQUEST_UNDO");
|
|
expect(model.getters.getListName("1")).toBe("List");
|
|
model.dispatch("REQUEST_REDO");
|
|
expect(model.getters.getListName("1")).toBe("test");
|
|
});
|
|
|
|
test("Can delete list", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
model.dispatch("REMOVE_ODOO_LIST", { listId: "1" });
|
|
expect(model.getters.getListIds().length).toBe(0);
|
|
const B4 = getEvaluatedCell(model, "B4");
|
|
expect(B4.message).toBe('There is no list with id "1"');
|
|
expect(B4.value).toBe("#ERROR");
|
|
});
|
|
|
|
test("Can undo/redo a delete list", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const value = getEvaluatedCell(model, "B4").value;
|
|
model.dispatch("REMOVE_ODOO_LIST", { listId: "1" });
|
|
model.dispatch("REQUEST_UNDO");
|
|
expect(model.getters.getListIds().length).toBe(1);
|
|
let B4 = getEvaluatedCell(model, "B4");
|
|
expect(B4.value).toBe(value);
|
|
model.dispatch("REQUEST_REDO");
|
|
expect(model.getters.getListIds().length).toBe(0);
|
|
B4 = getEvaluatedCell(model, "B4");
|
|
expect(B4.message).toBe('There is no list with id "1"');
|
|
expect(B4.value).toBe("#ERROR");
|
|
});
|
|
|
|
test("can update a list", async () => {
|
|
let spreadsheetLoaded = false;
|
|
let isInitialUpdate = true;
|
|
let isUndoUpdate = false;
|
|
const { model } = await createSpreadsheetWithList({
|
|
mockRPC: async function (route, args) {
|
|
if (
|
|
spreadsheetLoaded &&
|
|
args.method === "web_search_read" &&
|
|
args.model === "partner"
|
|
) {
|
|
expect.step("data-fetched");
|
|
if (isInitialUpdate) {
|
|
expect(args.kwargs.order).toBe("name DESC");
|
|
expect(args.kwargs.domain).toEqual([["name", "in", ["hola"]]]);
|
|
}
|
|
if (isUndoUpdate) {
|
|
expect(args.kwargs.order).toBe("");
|
|
expect(args.kwargs.domain).toEqual([]);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
const [listId] = model.getters.getListIds();
|
|
spreadsheetLoaded = true;
|
|
const listDef = model.getters.getListDefinition(listId);
|
|
const newListDef = {
|
|
name: "My Updated List",
|
|
metaData: {
|
|
resModel: listDef.model,
|
|
columns: listDef.columns,
|
|
},
|
|
searchParams: {
|
|
context: {},
|
|
orderBy: [{ name: "name", asc: false }],
|
|
domain: [["name", "in", ["hola"]]],
|
|
},
|
|
};
|
|
model.dispatch("UPDATE_ODOO_LIST", {
|
|
listId,
|
|
list: newListDef,
|
|
});
|
|
await waitForDataLoaded(model);
|
|
expect.verifySteps(["data-fetched"]);
|
|
const updatedListDef = model.getters.getListDefinition(listId);
|
|
expect(updatedListDef).toEqual({
|
|
id: listId,
|
|
name: newListDef.name,
|
|
model: newListDef.metaData.resModel,
|
|
actionXmlId: undefined,
|
|
columns: newListDef.metaData.columns,
|
|
context: {},
|
|
domain: newListDef.searchParams.domain,
|
|
orderBy: newListDef.searchParams.orderBy,
|
|
});
|
|
isInitialUpdate = false;
|
|
isUndoUpdate = true;
|
|
undo(model);
|
|
await waitForDataLoaded(model);
|
|
expect.verifySteps(["data-fetched"]);
|
|
expect(model.getters.getListDefinition(listId)).toEqual(listDef);
|
|
isUndoUpdate = false;
|
|
redo(model);
|
|
await waitForDataLoaded(model);
|
|
expect.verifySteps(["data-fetched"]);
|
|
expect(model.getters.getListDefinition(listId)).toEqual(updatedListDef);
|
|
});
|
|
|
|
test("can edit list domain", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const [listId] = model.getters.getListIds();
|
|
expect(model.getters.getListDefinition(listId).domain).toEqual([]);
|
|
expect(getCellValue(model, "B2")).toBe(true);
|
|
model.dispatch("UPDATE_ODOO_LIST_DOMAIN", {
|
|
listId,
|
|
domain: [["foo", "in", [55]]],
|
|
});
|
|
expect(model.getters.getListDefinition(listId).domain).toEqual([["foo", "in", [55]]]);
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "B2")).toBe("");
|
|
model.dispatch("REQUEST_UNDO");
|
|
await waitForDataLoaded(model);
|
|
expect(model.getters.getListDefinition(listId).domain).toEqual([]);
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "B2")).toBe(true);
|
|
model.dispatch("REQUEST_REDO");
|
|
expect(model.getters.getListDefinition(listId).domain).toEqual([["foo", "in", [55]]]);
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "B2")).toBe("");
|
|
});
|
|
|
|
test("can edit list sorting", async () => {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["foo", "bar", "date", "probability", "pognon"],
|
|
});
|
|
// prettier-ignore
|
|
const initialGrid = [
|
|
["Foo", "Bar", "Date", "Probability", "Money!"],
|
|
[12, true, 42474, 10, 74.4],
|
|
[1, true, 42669, 11, 74.8],
|
|
[17, true, 42719, 95, 4],
|
|
[2, false, 42715, 15, 1000],
|
|
]
|
|
// prettier-ignore
|
|
const orderedGrid = [
|
|
["Foo", "Bar", "Date", "Probability", "Money!"],
|
|
[17, true, 42719, 95, 4],
|
|
[12, true, 42474, 10, 74.4],
|
|
[1, true, 42669, 11, 74.8],
|
|
[2, false, 42715, 15, 1000],
|
|
]
|
|
const [listId] = model.getters.getListIds();
|
|
expect(model.getters.getListDefinition(listId).orderBy).toEqual([]);
|
|
expect(getEvaluatedGrid(model, "A1:E5")).toEqual(initialGrid);
|
|
const orderBy = [
|
|
{ name: "bar", asc: false },
|
|
{ name: "pognon", asc: true },
|
|
];
|
|
const listDefinition = model.getters.getListModelDefinition(listId);
|
|
model.dispatch("UPDATE_ODOO_LIST", {
|
|
listId,
|
|
list: {
|
|
...listDefinition,
|
|
searchParams: {
|
|
...listDefinition.searchParams,
|
|
orderBy,
|
|
},
|
|
},
|
|
});
|
|
await waitForDataLoaded(model);
|
|
expect(model.getters.getListDefinition(listId).orderBy).toEqual(orderBy);
|
|
expect(getEvaluatedGrid(model, "A1:E5")).toEqual(orderedGrid);
|
|
undo(model);
|
|
expect(model.getters.getListDefinition(listId).orderBy).toEqual([]);
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedGrid(model, "A1:E5")).toEqual(initialGrid);
|
|
redo(model);
|
|
await waitForDataLoaded(model);
|
|
expect(model.getters.getListDefinition(listId).orderBy).toEqual(orderBy);
|
|
expect(getEvaluatedGrid(model, "A1:E5")).toEqual(orderedGrid);
|
|
});
|
|
|
|
test("editing the sorting of a list of that does not exist should throw an error", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const result = model.dispatch("UPDATE_ODOO_LIST", {
|
|
listId: "invalid",
|
|
list: undefined,
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.ListIdNotFound]);
|
|
});
|
|
|
|
test("edited domain is exported", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const [listId] = model.getters.getListIds();
|
|
model.dispatch("UPDATE_ODOO_LIST_DOMAIN", {
|
|
listId,
|
|
domain: [["foo", "in", [55]]],
|
|
});
|
|
expect(model.exportData().lists["1"].domain).toEqual([["foo", "in", [55]]]);
|
|
const result = model.dispatch("UPDATE_ODOO_LIST_DOMAIN", {
|
|
listId: "invalid",
|
|
domain: [],
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.ListIdNotFound]);
|
|
});
|
|
|
|
test("edited list sorting is exported", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const [listId] = model.getters.getListIds();
|
|
const orderBy = [
|
|
{ name: "foo", asc: true },
|
|
{ name: "bar", asc: false },
|
|
];
|
|
const listDefinition = model.getters.getListModelDefinition(listId);
|
|
model.dispatch("UPDATE_ODOO_LIST", {
|
|
listId,
|
|
list: {
|
|
...listDefinition,
|
|
searchParams: {
|
|
...listDefinition.searchParams,
|
|
orderBy,
|
|
},
|
|
},
|
|
});
|
|
expect(model.exportData().lists["1"].orderBy).toEqual(orderBy);
|
|
});
|
|
|
|
test("Cannot see record of a list in dashboard mode if wrong list formula", async function () {
|
|
mockService("action", {
|
|
async doAction(params) {
|
|
expect.step(params.res_model);
|
|
expect.step(params.res_id.toString());
|
|
},
|
|
});
|
|
const { model } = await createSpreadsheetWithList();
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
model.dispatch("UPDATE_CELL", {
|
|
col: 0,
|
|
row: 1,
|
|
sheetId,
|
|
content: "=ODOO.LIST()",
|
|
});
|
|
model.updateMode("dashboard");
|
|
selectCell(model, "A2");
|
|
expect.verifySteps([]);
|
|
});
|
|
|
|
test("Can see record on vectorized list index", async function () {
|
|
mockService("action", {
|
|
async doAction(params) {
|
|
expect.step(`${params.res_model},${params.res_id}`);
|
|
},
|
|
});
|
|
const { model, env } = await createSpreadsheetWithList();
|
|
model.dispatch("CREATE_SHEET", { sheetId: "42" });
|
|
model.dispatch("ACTIVATE_SHEET", {
|
|
sheetIdFrom: model.getters.getActiveSheetId(),
|
|
sheetIdTo: "42",
|
|
});
|
|
setCellContent(model, "C1", "1");
|
|
setCellContent(model, "C2", "2");
|
|
setCellContent(model, "D1", "3");
|
|
setCellContent(model, "D2", "4");
|
|
setCellContent(model, "A1", '=ODOO.LIST(1, C1:D2, "foo")');
|
|
const seeRecordAction = cellMenuRegistry.getAll().find((item) => item.id === "list_see_record");
|
|
|
|
selectCell(model, "A1");
|
|
expect(seeRecordAction.isVisible(env)).toBe(true);
|
|
await seeRecordAction.execute(env);
|
|
expect.verifySteps(["partner,1"]);
|
|
|
|
selectCell(model, "A2");
|
|
expect(seeRecordAction.isVisible(env)).toBe(true);
|
|
await seeRecordAction.execute(env);
|
|
expect.verifySteps(["partner,2"]);
|
|
|
|
selectCell(model, "B1");
|
|
expect(seeRecordAction.isVisible(env)).toBe(true);
|
|
await seeRecordAction.execute(env);
|
|
expect.verifySteps(["partner,3"]);
|
|
|
|
selectCell(model, "B2");
|
|
expect(seeRecordAction.isVisible(env)).toBe(true);
|
|
await seeRecordAction.execute(env);
|
|
expect.verifySteps(["partner,4"]);
|
|
});
|
|
|
|
test("field matching is removed when filter is deleted", async function () {
|
|
const { model } = await createSpreadsheetWithList();
|
|
await addGlobalFilter(
|
|
model,
|
|
{
|
|
id: "42",
|
|
type: "relation",
|
|
label: "test",
|
|
defaultValue: [41],
|
|
modelName: undefined,
|
|
rangeType: undefined,
|
|
},
|
|
{
|
|
list: { 1: { chain: "product_id", type: "many2one" } },
|
|
}
|
|
);
|
|
const [filter] = model.getters.getGlobalFilters();
|
|
const matching = {
|
|
chain: "product_id",
|
|
type: "many2one",
|
|
};
|
|
expect(model.getters.getListFieldMatching("1", filter.id)).toEqual(matching);
|
|
expect(model.getters.getListDataSource("1").getComputedDomain()).toEqual([
|
|
["product_id", "in", [41]],
|
|
]);
|
|
model.dispatch("REMOVE_GLOBAL_FILTER", {
|
|
id: filter.id,
|
|
});
|
|
expect(model.getters.getListFieldMatching("1", filter.id)).toBe(undefined, {
|
|
message: "it should have removed the pivot and its fieldMatching and datasource altogether",
|
|
});
|
|
expect(model.getters.getListDataSource("1").getComputedDomain()).toEqual([]);
|
|
model.dispatch("REQUEST_UNDO");
|
|
expect(model.getters.getListFieldMatching("1", filter.id)).toEqual(matching);
|
|
expect(model.getters.getListDataSource("1").getComputedDomain()).toEqual([
|
|
["product_id", "in", [41]],
|
|
]);
|
|
model.dispatch("REQUEST_REDO");
|
|
expect(model.getters.getListFieldMatching("1", filter.id)).toBe(undefined);
|
|
expect(model.getters.getListDataSource("1").getComputedDomain()).toEqual([]);
|
|
});
|
|
|
|
test("Preload currency of monetary field", async function () {
|
|
await createSpreadsheetWithList({
|
|
columns: ["pognon"],
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "web_search_read" && args.model === "partner") {
|
|
const spec = args.kwargs.specification;
|
|
expect(Object.keys(spec).length).toBe(2);
|
|
expect(spec.currency_id).toEqual({
|
|
fields: {
|
|
name: {},
|
|
symbol: {},
|
|
decimal_places: {},
|
|
position: {},
|
|
},
|
|
});
|
|
expect(spec.pognon).toEqual({});
|
|
}
|
|
},
|
|
});
|
|
});
|
|
|
|
test("fetch all and only required fields", async function () {
|
|
const spreadsheetData = {
|
|
sheets: [
|
|
{
|
|
id: "sheet1",
|
|
cells: {
|
|
A1: { content: '=ODOO.LIST(1, 1, "foo")' }, // in the definition
|
|
A2: { content: '=ODOO.LIST(1, 1, "product_id")' }, // not in the definition
|
|
A3: { content: '=ODOO.LIST(1, 1, "invalid_field")' },
|
|
},
|
|
},
|
|
],
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["foo", "contact_name"],
|
|
domain: [],
|
|
model: "partner",
|
|
orderBy: [],
|
|
context: {},
|
|
},
|
|
},
|
|
};
|
|
await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === "web_search_read" && args.model === "partner") {
|
|
expect.step("data-fetched");
|
|
expect(args.kwargs.specification).toEqual({
|
|
foo: {},
|
|
product_id: {
|
|
fields: {
|
|
display_name: {},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
},
|
|
});
|
|
expect.verifySteps(["data-fetched"]);
|
|
});
|
|
|
|
test("fetch all required positions, including the evaluated ones", async function () {
|
|
const spreadsheetData = {
|
|
sheets: [
|
|
{
|
|
id: "sheet1",
|
|
cells: {
|
|
A1: { content: '=ODOO.LIST(1, 11, "foo")' },
|
|
A2: { content: '=ODOO.LIST(1, A3, "foo")' },
|
|
A3: { content: "111" },
|
|
},
|
|
},
|
|
],
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["foo"],
|
|
domain: [],
|
|
model: "partner",
|
|
orderBy: [],
|
|
context: {},
|
|
},
|
|
},
|
|
};
|
|
await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === "web_search_read" && args.model === "partner") {
|
|
expect.step("data-fetched");
|
|
expect(args.kwargs.limit).toBe(111);
|
|
}
|
|
},
|
|
});
|
|
expect.verifySteps(["data-fetched"]);
|
|
});
|
|
|
|
test("list with both a monetary field and the related currency field 1", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["pognon", "currency_id"],
|
|
});
|
|
setCellContent(model, "A1", '=ODOO.LIST(1, 1, "pognon")');
|
|
setCellContent(model, "A2", '=ODOO.LIST(1, 1, "currency_id")');
|
|
await animationFrame();
|
|
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("74.40€");
|
|
expect(getEvaluatedCell(model, "A2").value).toBe("EUR");
|
|
});
|
|
|
|
test("list with both a monetary field and the related currency field 2", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
columns: ["currency_id", "pognon"],
|
|
});
|
|
setCellContent(model, "A1", '=ODOO.LIST(1, 1, "pognon")');
|
|
setCellContent(model, "A2", '=ODOO.LIST(1, 1, "currency_id")');
|
|
await animationFrame();
|
|
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("74.40€");
|
|
expect(getEvaluatedCell(model, "A2").value).toBe("EUR");
|
|
});
|
|
|
|
test("List record limit is computed during the import and UPDATE_CELL", async function () {
|
|
const spreadsheetData = {
|
|
sheets: [
|
|
{
|
|
id: "sheet1",
|
|
cells: {
|
|
A1: { content: `=ODOO.LIST("1", "1", "foo")` },
|
|
},
|
|
},
|
|
],
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["foo", "contact_name"],
|
|
domain: [],
|
|
model: "partner",
|
|
orderBy: [],
|
|
context: {},
|
|
},
|
|
},
|
|
};
|
|
const model = await createModelWithDataSource({ spreadsheetData });
|
|
const ds = model.getters.getListDataSource("1");
|
|
expect(ds.maxPosition).toBe(1);
|
|
expect(ds.maxPositionFetched).toBe(1);
|
|
setCellContent(model, "A1", `=ODOO.LIST("1", "42", "foo")`);
|
|
expect(ds.maxPosition).toBe(42);
|
|
expect(ds.maxPositionFetched).toBe(1);
|
|
await waitForDataLoaded(model);
|
|
expect(ds.maxPosition).toBe(42);
|
|
expect(ds.maxPositionFetched).toBe(42);
|
|
});
|
|
|
|
test("Spec of web_search_read is minimal", async function () {
|
|
const spreadsheetData = {
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["currency_id", "pognon", "foo"],
|
|
model: "partner",
|
|
orderBy: [],
|
|
},
|
|
},
|
|
};
|
|
const model = await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === "web_search_read") {
|
|
expect(args.kwargs.specification).toEqual({
|
|
pognon: {},
|
|
currency_id: {
|
|
fields: {
|
|
name: {},
|
|
symbol: {},
|
|
decimal_places: {},
|
|
display_name: {},
|
|
position: {},
|
|
},
|
|
},
|
|
foo: {},
|
|
});
|
|
expect.step("web_search_read");
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", '=ODOO.LIST(1, 1, "pognon")');
|
|
setCellContent(model, "A2", '=ODOO.LIST(1, 1, "currency_id")');
|
|
setCellContent(model, "A3", '=ODOO.LIST(1, 1, "foo")');
|
|
await waitForDataLoaded(model);
|
|
expect.verifySteps(["web_search_read"]);
|
|
});
|
|
|
|
test("can import (export) contextual domain", async function () {
|
|
const uid = user.userId;
|
|
const spreadsheetData = {
|
|
lists: {
|
|
1: {
|
|
id: 1,
|
|
columns: ["foo", "contact_name"],
|
|
domain: '[("foo", "=", uid)]',
|
|
model: "partner",
|
|
orderBy: [],
|
|
},
|
|
},
|
|
};
|
|
const model = await createModelWithDataSource({
|
|
spreadsheetData,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === "web_search_read") {
|
|
expect(args.kwargs.domain).toEqual([["foo", "=", uid]]);
|
|
expect.step("web_search_read");
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", '=ODOO.LIST("1", "1", "foo")');
|
|
await animationFrame();
|
|
expect(model.exportData().lists[1].domain).toBe('[("foo", "=", uid)]', {
|
|
message: "the domain is exported with the dynamic parts",
|
|
});
|
|
expect.verifySteps(["web_search_read"]);
|
|
});
|
|
|
|
test("Load list spreadsheet with models that cannot be accessed", async function () {
|
|
let hasAccessRights = true;
|
|
const { model } = await createSpreadsheetWithList({
|
|
mockRPC: async function (route, args) {
|
|
if (args.model === "partner" && args.method === "web_search_read" && !hasAccessRights) {
|
|
throw makeServerError({ description: "ya done!" });
|
|
}
|
|
},
|
|
});
|
|
let headerCell;
|
|
let cell;
|
|
await waitForDataLoaded(model);
|
|
headerCell = getEvaluatedCell(model, "A3");
|
|
cell = getEvaluatedCell(model, "C3");
|
|
|
|
expect(headerCell.value).toBe(1);
|
|
expect(cell.value).toBe(42669);
|
|
|
|
hasAccessRights = false;
|
|
model.dispatch("REFRESH_ALL_DATA_SOURCES");
|
|
await waitForDataLoaded(model);
|
|
headerCell = getEvaluatedCell(model, "A3");
|
|
cell = getEvaluatedCell(model, "C3");
|
|
|
|
expect(headerCell.value).toBe("#ERROR");
|
|
expect(headerCell.message).toBe("ya done!");
|
|
expect(cell.value).toBe("#ERROR");
|
|
expect(cell.message).toBe("ya done!");
|
|
});
|
|
|
|
test("Can duplicate a list", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const [listId] = model.getters.getListIds();
|
|
const filter = { ...THIS_YEAR_GLOBAL_FILTER, id: "42" };
|
|
const matching = { chain: "product_id", type: "many2one" };
|
|
await addGlobalFilter(model, filter, {
|
|
list: { [listId]: matching },
|
|
});
|
|
model.dispatch("DUPLICATE_ODOO_LIST", { listId, newListId: "2" });
|
|
|
|
const listIds = model.getters.getListIds();
|
|
expect(model.getters.getListIds().length).toBe(2);
|
|
|
|
const expectedDuplicatedDefinition = {
|
|
...model.getters.getListDefinition(listId),
|
|
id: "2",
|
|
};
|
|
expect(model.getters.getListDefinition(listIds[1])).toEqual(expectedDuplicatedDefinition);
|
|
|
|
expect(model.getters.getListFieldMatching(listId, "42")).toEqual(matching);
|
|
expect(model.getters.getListFieldMatching("2", "42")).toEqual(matching);
|
|
});
|
|
|
|
test("Cannot duplicate unknown list", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const result = model.dispatch("DUPLICATE_ODOO_LIST", {
|
|
listId: "hello",
|
|
newListId: model.getters.getNextListId(),
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.ListIdNotFound]);
|
|
});
|
|
|
|
test("Cannot duplicate list with id different from nextId", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const [listId] = model.getters.getListIds();
|
|
const result = model.dispatch("DUPLICATE_ODOO_LIST", {
|
|
listId,
|
|
newListId: "66",
|
|
});
|
|
expect(result.reasons).toEqual([CommandResult.InvalidNextId]);
|
|
});
|
|
|
|
test("isListUnused getter", async () => {
|
|
const { model } = await createSpreadsheetWithList();
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
expect(model.getters.isListUnused("1")).toBe(false);
|
|
|
|
model.dispatch("CREATE_SHEET", { sheetId: "2" });
|
|
model.dispatch("DELETE_SHEET", { sheetId: sheetId });
|
|
expect(model.getters.isListUnused("1")).toBe(true);
|
|
|
|
setCellContent(model, "A1", '=ODOO.LIST.HEADER(1, "foo")');
|
|
expect(model.getters.isListUnused("1")).toBe(false);
|
|
|
|
setCellContent(model, "A1", '=ODOO.LIST.HEADER(A2, "foo")');
|
|
expect(model.getters.isListUnused("1")).toBe(true);
|
|
|
|
setCellContent(model, "A2", "1");
|
|
expect(model.getters.isListUnused("1")).toBe(false);
|
|
|
|
model.dispatch("REQUEST_UNDO", {});
|
|
expect(model.getters.isListUnused("1")).toBe(true);
|
|
});
|
|
|
|
test("INSERT_ODOO_LIST_WITH_TABLE adds a table that maches the list dimension", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
linesNumber: 4,
|
|
});
|
|
const sheetId = model.getters.getActiveSheetId();
|
|
const { columns: currentColumns, model: resModel } = model.getters.getListDefinition("1");
|
|
const col = 0;
|
|
const row = 19;
|
|
const threshold = 5;
|
|
const { definition, columns } = generateListDefinition(resModel, currentColumns);
|
|
model.dispatch("INSERT_ODOO_LIST_WITH_TABLE", {
|
|
sheetId,
|
|
col,
|
|
row,
|
|
id: model.getters.getNextListId(),
|
|
definition,
|
|
linesNumber: threshold,
|
|
columns,
|
|
});
|
|
const table = model.getters.getTable({ sheetId, col, row });
|
|
expect(table.range.zone).toEqual(toZone("A20:D25"));
|
|
expect(table.type).toBe("static");
|
|
expect(table.config).toEqual({ ...PIVOT_TABLE_CONFIG, firstColumn: false });
|
|
});
|
|
|
|
test("An error is displayed if the list has invalid model", async function () {
|
|
const { model } = await createSpreadsheetWithList({
|
|
mockRPC: async function (route, { model, method, kwargs }) {
|
|
if (model === "unknown" && method === "fields_get") {
|
|
throw makeServerError({ code: 404 });
|
|
}
|
|
},
|
|
});
|
|
const listId = model.getters.getListIds()[0];
|
|
const listDefinition = model.getters.getListModelDefinition(listId);
|
|
model.dispatch("UPDATE_ODOO_LIST", {
|
|
listId,
|
|
list: {
|
|
...listDefinition,
|
|
metaData: {
|
|
...listDefinition.metaData,
|
|
resModel: "unknown",
|
|
},
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.LIST(1,1,"foo")`);
|
|
await animationFrame();
|
|
expect(getCellValue(model, "A1")).toBe("#ERROR");
|
|
expect(getEvaluatedCell(model, "A1").message).toBe(`The model "unknown" does not exist.`);
|
|
});
|