449 lines
18 KiB
JavaScript
449 lines
18 KiB
JavaScript
import { describe, expect, test } from "@odoo/hoot";
|
|
import { setCellContent } from "@spreadsheet/../tests/helpers/commands";
|
|
import { getCellValue, getEvaluatedCell } from "@spreadsheet/../tests/helpers/getters";
|
|
import { createModelWithDataSource } from "@spreadsheet/../tests/helpers/model";
|
|
import { camelToSnakeObject } from "@spreadsheet/helpers/helpers";
|
|
import {
|
|
defineSpreadsheetAccountModels,
|
|
getAccountingData,
|
|
} from "@spreadsheet_account/../tests/accounting_test_data";
|
|
import { parseAccountingDate } from "@spreadsheet_account/accounting_functions";
|
|
import { makeServerError } from "@web/../tests/web_test_helpers";
|
|
import { sprintf } from "@web/core/utils/strings";
|
|
|
|
import * as spreadsheet from "@odoo/o-spreadsheet";
|
|
import { waitForDataLoaded } from "@spreadsheet/helpers/model";
|
|
|
|
describe.current.tags("headless");
|
|
defineSpreadsheetAccountModels();
|
|
|
|
const { DEFAULT_LOCALE: locale } = spreadsheet.constants;
|
|
|
|
const serverData = getAccountingData();
|
|
|
|
test("Basic evaluation", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
return [{ debit: 42, credit: 16 }];
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
|
setCellContent(model, "A2", `=ODOO.DEBIT("100", "2022")`);
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100", "2022")`);
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "A1")).toBe(16);
|
|
expect(getCellValue(model, "A2")).toBe(42);
|
|
expect(getCellValue(model, "A3")).toBe(26);
|
|
expect.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
|
});
|
|
|
|
test("evaluation with reference to a month period", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect(args.args[0]).toEqual([
|
|
{
|
|
codes: ["100"],
|
|
company_id: null,
|
|
date_range: {
|
|
month: 2,
|
|
range_type: "month",
|
|
year: 2022,
|
|
},
|
|
include_unposted: false,
|
|
},
|
|
]);
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
return [{ debit: 42, credit: 16 }];
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "B1", "02/2022");
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100", B1)`);
|
|
setCellContent(model, "A2", `=ODOO.DEBIT("100", B1)`);
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100", B1)`);
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "A1")).toBe(16);
|
|
expect(getCellValue(model, "A2")).toBe(42);
|
|
expect(getCellValue(model, "A3")).toBe(26);
|
|
expect(getCellValue(model, "B1")).toBe(44593);
|
|
expect.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
|
});
|
|
|
|
test("Functions are correctly formatted", async () => {
|
|
const model = await createModelWithDataSource();
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
|
setCellContent(model, "A2", `=ODOO.DEBIT("100", "2022")`);
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100", "2022")`);
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedCell(model, "A1").format).toBe("#,##0.00[$€]");
|
|
expect(getEvaluatedCell(model, "A2").format).toBe("#,##0.00[$€]");
|
|
expect(getEvaluatedCell(model, "A3").format).toBe("#,##0.00[$€]");
|
|
});
|
|
|
|
test("Functions with a wrong company id is correctly in error", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "get_company_currency_for_spreadsheet") {
|
|
return false;
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022", 0, 123456)`);
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedCell(model, "A1").message).toBe("Currency not available for this company.");
|
|
});
|
|
|
|
test("formula with invalid date", async () => {
|
|
const model = await createModelWithDataSource();
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100",)`);
|
|
setCellContent(model, "A2", `=ODOO.DEBIT("100", 0)`);
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100", -1)`);
|
|
setCellContent(model, "A4", `=ODOO.BALANCE("100", "not a valid period")`);
|
|
setCellContent(model, "A5", `=ODOO.BALANCE("100", 1900)`); // this should be ok
|
|
setCellContent(model, "A6", `=ODOO.BALANCE("100", 1900, -1)`);
|
|
setCellContent(model, "A7", `=ODOO.DEBIT("100", 1899)`);
|
|
await waitForDataLoaded(model);
|
|
const errorMessage = `'%s' is not a valid period. Supported formats are "21/12/2022", "Q1/2022", "12/2022", and "2022".`;
|
|
expect(getEvaluatedCell(model, "A1").message).toBe("0 is not a valid year.");
|
|
expect(getEvaluatedCell(model, "A2").message).toBe("0 is not a valid year.");
|
|
expect(getEvaluatedCell(model, "A3").message).toBe("-1 is not a valid year.");
|
|
expect(getEvaluatedCell(model, "A4").message).toBe(sprintf(errorMessage, "not a valid period"));
|
|
expect(getEvaluatedCell(model, "A5").value).toBe(0);
|
|
expect(getEvaluatedCell(model, "A6").message).toBe("1899 is not a valid year.");
|
|
expect(getEvaluatedCell(model, "A7").message).toBe("1899 is not a valid year.");
|
|
});
|
|
|
|
test("Evaluation with multiple account codes", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
return [{ debit: 142, credit: 26 }];
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100,200", "2022")`);
|
|
setCellContent(model, "A2", `=ODOO.DEBIT("100,200", "2022")`);
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100,200", "2022")`);
|
|
|
|
// with spaces
|
|
setCellContent(model, "B1", `=ODOO.CREDIT("100 , 200", "2022")`);
|
|
setCellContent(model, "B2", `=ODOO.DEBIT("100 , 200", "2022")`);
|
|
setCellContent(model, "B3", `=ODOO.BALANCE("100 , 200", "2022")`);
|
|
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "A1")).toBe(26);
|
|
expect(getCellValue(model, "A2")).toBe(142);
|
|
expect(getCellValue(model, "A3")).toBe(116);
|
|
|
|
expect(getCellValue(model, "B1")).toBe(26);
|
|
expect(getCellValue(model, "B2")).toBe(142);
|
|
expect(getCellValue(model, "B3")).toBe(116);
|
|
expect.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
|
});
|
|
|
|
test("Handle error evaluation", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
throw makeServerError({ description: "a nasty error" });
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.CREDIT("100", "2022")`);
|
|
await waitForDataLoaded(model);
|
|
const cell = getEvaluatedCell(model, "A1");
|
|
expect(cell.value).toBe("#ERROR");
|
|
expect(cell.message).toBe("a nasty error");
|
|
});
|
|
|
|
test("Server requests", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
const blobs = args.args[0];
|
|
for (const blob of blobs) {
|
|
expect.step(JSON.stringify(blob));
|
|
}
|
|
return new Array(blobs.length).fill({ credit: 0, debit: 0 });
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.BALANCE("100", "2022")`);
|
|
setCellContent(model, "A2", `=ODOO.CREDIT("100", "01/2022")`);
|
|
setCellContent(model, "A3", `=ODOO.DEBIT("100","Q2/2022")`);
|
|
setCellContent(model, "A4", `=ODOO.BALANCE("10", "2021")`);
|
|
setCellContent(model, "A5", `=ODOO.CREDIT("10", "2022", -1)`); // same payload as A4: should only be called once
|
|
setCellContent(model, "A6", `=ODOO.DEBIT("5", "2021", 0, 2)`);
|
|
setCellContent(model, "A7", `=ODOO.DEBIT("5", "05/04/2021", 1)`);
|
|
setCellContent(model, "A8", `=ODOO.BALANCE("5", "2022",,,FALSE)`);
|
|
setCellContent(model, "A9", `=ODOO.BALANCE("100", "05/05/2022",,,TRUE)`);
|
|
setCellContent(model, "A10", `=ODOO.BALANCE(33,2021,-2)`);
|
|
await waitForDataLoaded(model);
|
|
|
|
expect.verifySteps([
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022" }, locale),
|
|
codes: ["100"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "01/2022" }, locale),
|
|
codes: ["100"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "Q2/2022" }, locale),
|
|
codes: ["100"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2021" }, locale),
|
|
codes: ["10"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2021" }, locale),
|
|
codes: ["5"],
|
|
companyId: 2,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "05/04/2022" }, locale),
|
|
codes: ["5"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022" }, locale),
|
|
codes: ["5"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "05/05/2022" }, locale),
|
|
codes: ["100"],
|
|
companyId: null,
|
|
includeUnposted: true,
|
|
})
|
|
),
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2019" }, locale),
|
|
codes: ["33"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
]);
|
|
});
|
|
|
|
test("Server requests with multiple account codes", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
const blobs = args.args[0];
|
|
for (const blob of blobs) {
|
|
expect.step(JSON.stringify(blob));
|
|
}
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.BALANCE("100,200", "2022")`);
|
|
setCellContent(model, "A2", `=ODOO.CREDIT("100,200", "2022")`);
|
|
setCellContent(model, "A3", `=ODOO.DEBIT("100,200","2022")`);
|
|
await waitForDataLoaded(model);
|
|
|
|
expect.verifySteps([
|
|
"spreadsheet_fetch_debit_credit",
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022"}, locale),
|
|
codes: ["100", "200"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
]);
|
|
});
|
|
|
|
test("account group formula as input to balance formula", async () => {
|
|
const model = await createModelWithDataSource({
|
|
serverData,
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
const blobs = args.args[0];
|
|
for (const blob of blobs) {
|
|
expect.step(JSON.stringify(blob));
|
|
}
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income")`);
|
|
setCellContent(model, "A2", `=ODOO.BALANCE(A1, 2022)`);
|
|
expect(getCellValue(model, "A1")).toBe("Loading...");
|
|
expect(getCellValue(model, "A2")).toBe("Loading...");
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "A1")).toBe("100104,200104");
|
|
expect(getCellValue(model, "A2")).toBe(0);
|
|
expect.verifySteps([
|
|
"spreadsheet_fetch_debit_credit",
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022"}, locale),
|
|
codes: ["100104", "200104"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
]);
|
|
});
|
|
|
|
test("two concurrent requests on different accounts", async () => {
|
|
const model = await createModelWithDataSource({
|
|
serverData,
|
|
mockRPC: async function (route, args) {
|
|
if (args.method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
const blobs = args.args[0];
|
|
for (const blob of blobs) {
|
|
expect.step(JSON.stringify(blob));
|
|
}
|
|
}
|
|
},
|
|
});
|
|
setCellContent(model, "A1", `=ODOO.ACCOUNT.GROUP("income")`);
|
|
setCellContent(model, "A2", `=ODOO.BALANCE(A1, 2022)`); // batched only when A1 resolves
|
|
setCellContent(model, "A3", `=ODOO.BALANCE("100", 2022)`); // batched directly
|
|
expect(getCellValue(model, "A1")).toBe("Loading...");
|
|
expect(getCellValue(model, "A2")).toBe("Loading...");
|
|
expect(getCellValue(model, "A3")).toBe("Loading...");
|
|
// A lot happens within the next tick.
|
|
// Because cells are evaluated given their order in the sheet,
|
|
// A1's request is done first, meaning it's also resolved first, which add A2 to the next batch (synchronously)
|
|
// Only then A3 is resolved. => A2 is batched while A3 is pending
|
|
await waitForDataLoaded(model);
|
|
expect(getCellValue(model, "A1")).toBe("100104,200104");
|
|
expect(getCellValue(model, "A2")).toBe(0);
|
|
expect(getCellValue(model, "A3")).toBe(0);
|
|
expect.verifySteps([
|
|
"spreadsheet_fetch_debit_credit",
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022" }, locale),
|
|
codes: ["100"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
"spreadsheet_fetch_debit_credit",
|
|
JSON.stringify(
|
|
camelToSnakeObject({
|
|
dateRange: parseAccountingDate({ value: "2022" }, locale),
|
|
codes: ["100104", "200104"],
|
|
companyId: null,
|
|
includeUnposted: false,
|
|
})
|
|
),
|
|
]);
|
|
});
|
|
|
|
test("date with non-standard locale", async () => {
|
|
const model = await createModelWithDataSource({
|
|
mockRPC: async function (route, { method, args }) {
|
|
if (method === "spreadsheet_fetch_debit_credit") {
|
|
expect.step("spreadsheet_fetch_debit_credit");
|
|
expect(args).toEqual([
|
|
[
|
|
{
|
|
codes: ["100"],
|
|
company_id: null,
|
|
date_range: {
|
|
range_type: "day",
|
|
year: 2002,
|
|
month: 2,
|
|
day: 1,
|
|
},
|
|
include_unposted: false,
|
|
},
|
|
],
|
|
]);
|
|
return [{ debit: 142, credit: 26 }];
|
|
}
|
|
},
|
|
});
|
|
const myLocale = { ...locale, dateFormat: "d/mmm/yyyy" };
|
|
model.dispatch("UPDATE_LOCALE", { locale: myLocale });
|
|
setCellContent(model, "A1", "=DATE(2002, 2, 1)");
|
|
setCellContent(model, "A2", "=ODOO.BALANCE(100, A1)");
|
|
setCellContent(model, "A3", "=ODOO.CREDIT(100, A1)");
|
|
setCellContent(model, "A4", "=ODOO.DEBIT(100, A1)");
|
|
await waitForDataLoaded(model);
|
|
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("1/Feb/2002");
|
|
expect(getCellValue(model, "A2")).toBe(116);
|
|
expect(getCellValue(model, "A3")).toBe(26);
|
|
expect(getCellValue(model, "A4")).toBe(142);
|
|
expect.verifySteps(["spreadsheet_fetch_debit_credit"]);
|
|
});
|
|
|
|
test("parseAccountingDate", () => {
|
|
expect(parseAccountingDate({ value: "2022" }, locale)).toEqual({
|
|
rangeType: "year",
|
|
year: 2022,
|
|
});
|
|
expect(parseAccountingDate({ value: "11/10/2022" }, locale)).toEqual({
|
|
rangeType: "day",
|
|
year: 2022,
|
|
month: 11,
|
|
day: 10,
|
|
});
|
|
expect(parseAccountingDate({ value: "10/2022" }, locale)).toEqual({
|
|
rangeType: "month",
|
|
year: 2022,
|
|
month: 10,
|
|
});
|
|
expect(parseAccountingDate({ value: "Q1/2022" }, locale)).toEqual({
|
|
rangeType: "quarter",
|
|
year: 2022,
|
|
quarter: 1,
|
|
});
|
|
expect(parseAccountingDate({ value: "q4/2022" }, locale)).toEqual({
|
|
rangeType: "quarter",
|
|
year: 2022,
|
|
quarter: 4,
|
|
});
|
|
// A number below 3000 is interpreted as a year.
|
|
// It's interpreted as a regular spreadsheet date otherwise
|
|
expect(parseAccountingDate({ value: "3005" }, locale)).toEqual({
|
|
rangeType: "day",
|
|
year: 1908,
|
|
month: 3,
|
|
day: 23,
|
|
});
|
|
});
|