446 lines
16 KiB
JavaScript
446 lines
16 KiB
JavaScript
import { describe, before, expect, test } from "@odoo/hoot";
|
|
import { animationFrame } from "@odoo/hoot-mock";
|
|
|
|
import { Model } from "@odoo/o-spreadsheet";
|
|
|
|
import { x2ManyCommands } from "@web/core/orm_service";
|
|
|
|
import {
|
|
addColumns,
|
|
addRows,
|
|
autofill,
|
|
undo,
|
|
redo,
|
|
copy,
|
|
cut,
|
|
paste,
|
|
deleteColumns,
|
|
setCellContent,
|
|
} from "@spreadsheet/../tests/helpers/commands";
|
|
import { getCellContent } from "@spreadsheet/../tests/helpers/getters";
|
|
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
|
import { defineModels, onRpc } from "@web/../tests/web_test_helpers";
|
|
import {
|
|
addFieldSync,
|
|
deleteFieldSyncs,
|
|
createSaleOrderSpreadsheetModel,
|
|
} from "./helpers/commands";
|
|
import { addSpreadsheetFieldSyncExtensionWithCleanUp } from "../src/bundle/field_sync/field_sync_extension_hook";
|
|
import { getFieldSync } from "./helpers/getters";
|
|
import { SaleOrderLine, defineSpreadsheetSaleModels } from "./helpers/data";
|
|
|
|
describe.current.tags("headless");
|
|
|
|
defineModels(mailModels);
|
|
defineSpreadsheetSaleModels();
|
|
|
|
before(() => {
|
|
addSpreadsheetFieldSyncExtensionWithCleanUp();
|
|
});
|
|
|
|
test("add a field sync", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
const fieldSync = getFieldSync(model, "A1");
|
|
expect(fieldSync).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
undo(model);
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
redo(model);
|
|
expect(getFieldSync(model, "A1")).toEqual(fieldSync);
|
|
});
|
|
|
|
test("delete a field sync", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
const fieldSync = getFieldSync(model, "A1");
|
|
deleteFieldSyncs(model, "A1");
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
undo(model);
|
|
expect(getFieldSync(model, "A1")).toBe(fieldSync);
|
|
redo(model);
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
});
|
|
|
|
test("export/import a field sync", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
const data = model.exportData();
|
|
expect(data.sheets[0].fieldSyncs["A1"]).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
const newModel = new Model(data, { custom: model.config.custom });
|
|
expect(getFieldSync(newModel, "A1")).toEqual(getFieldSync(model, "A1"));
|
|
});
|
|
|
|
test("can't add same field sync twice", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
const result = addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
expect(result.isSuccessful).toBe(false);
|
|
});
|
|
|
|
test("can't delete field sync that doesn't exist", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
expect(deleteFieldSyncs(model, "A1").isSuccessful).toBe(false);
|
|
});
|
|
|
|
test("field sync is moved when column is added before", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addColumns(model, "before", "A", 1);
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
expect(getFieldSync(model, "B1")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("field sync is moved when row is added before", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addFieldSync(model, "A2", "product_uom_qty", 1);
|
|
addRows(model, "before", 0, 1);
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
expect(getFieldSync(model, "A2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "A3")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 1,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("field sync is deleted when column is removed", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
deleteColumns(model, ["A"]);
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
expect(model.getters.getAllFieldSyncs().size).toBe(0);
|
|
});
|
|
|
|
test("x2many commands when the cell is empty", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
expect(getCellContent(model, "A1")).toBe("");
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("x2many commands when the cell is a number", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
setCellContent(model, "A1", "111");
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [
|
|
x2ManyCommands.update(42, {
|
|
product_uom_qty: 111,
|
|
}),
|
|
],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("x2many commands with field sync position bigger than formula", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }, { id: 43 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addFieldSync(model, "A2", "product_uom_qty", 1); // has a matching record but not loaded
|
|
addFieldSync(model, "A3", "product_uom_qty", 2); // doesn't have a matching record
|
|
setCellContent(model, "A1", "111");
|
|
setCellContent(model, "A2", "112");
|
|
setCellContent(model, "B1", '=ODOO.LIST(1, 1, "order_id")');
|
|
await animationFrame();
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [
|
|
x2ManyCommands.update(42, {
|
|
product_uom_qty: 111,
|
|
}),
|
|
x2ManyCommands.update(43, {
|
|
product_uom_qty: 112,
|
|
}),
|
|
],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("load order lines list only once", async () => {
|
|
onRpc("web_search_read", () => {
|
|
expect.step("web_search_read");
|
|
});
|
|
SaleOrderLine._records = [{ id: 42 }, { id: 43 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addFieldSync(model, "A2", "product_uom_qty", 1);
|
|
setCellContent(model, "A1", "111");
|
|
setCellContent(model, "A2", "112");
|
|
setCellContent(model, "B1", '=ODOO.LIST(1, 1, "order_id")');
|
|
setCellContent(model, "B1", '=ODOO.LIST(1, 2, "order_id")');
|
|
await animationFrame();
|
|
expect.verifySteps(["web_search_read"]);
|
|
await model.getters.getFieldSyncX2ManyCommands();
|
|
expect.verifySteps([]);
|
|
|
|
// add a field sync beyond the loaded records
|
|
addFieldSync(model, "A3", "product_uom_qty", 2);
|
|
await model.getters.getFieldSyncX2ManyCommands();
|
|
expect.verifySteps(["web_search_read"]);
|
|
});
|
|
|
|
test("x2many commands on 2 fields", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addFieldSync(model, "A2", "qty_delivered", 0);
|
|
setCellContent(model, "A1", "111");
|
|
setCellContent(model, "A2", "222");
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [
|
|
x2ManyCommands.update(42, {
|
|
product_uom_qty: 111,
|
|
qty_delivered: 222,
|
|
}),
|
|
],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("x2many commands when the cell is an error", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
setCellContent(model, "A1", "=1/0");
|
|
expect((await model.getters.getFieldSyncX2ManyCommands()).errors).toEqual([
|
|
'The value of A1 (#DIV/0!) can\'t be used for field "Product uom qty". It should be a number.',
|
|
]);
|
|
});
|
|
|
|
test("x2many commands text value on a numeric field", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
setCellContent(model, "A1", "Hello");
|
|
expect((await model.getters.getFieldSyncX2ManyCommands()).errors).toEqual([
|
|
'The value of A1 (Hello) can\'t be used for field "Product uom qty". It should be a number.',
|
|
]);
|
|
});
|
|
|
|
test("x2many commands numeric value on a text field", async () => {
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "name", 0);
|
|
setCellContent(model, "A1", "30%");
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [
|
|
x2ManyCommands.update(42, {
|
|
name: "30%",
|
|
}),
|
|
],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("x2many commands with not enough records in the list", async () => {
|
|
// only one record
|
|
SaleOrderLine._records = [{ id: 42 }];
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
setCellContent(model, "A1", "111");
|
|
|
|
// doesn't match any record in the list
|
|
addFieldSync(model, "A2", "product_uom_qty", 1);
|
|
setCellContent(model, "A2", "222");
|
|
|
|
expect(await model.getters.getFieldSyncX2ManyCommands()).toEqual({
|
|
commands: [
|
|
x2ManyCommands.update(42, {
|
|
product_uom_qty: 111,
|
|
}),
|
|
],
|
|
errors: [],
|
|
});
|
|
});
|
|
|
|
test("auto fill down increments position", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
autofill(model, "A1", "A3");
|
|
expect(getFieldSync(model, "A2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 1,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "A3")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 2,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("auto fill up decrements position", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A3", "product_uom_qty", 2);
|
|
autofill(model, "A3", "A1");
|
|
expect(getFieldSync(model, "A2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 1,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "A1")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("auto fill up stops at 0", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A3", "product_uom_qty", 1);
|
|
autofill(model, "A3", "A1");
|
|
expect(getFieldSync(model, "A2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "A1")).toBe(undefined);
|
|
});
|
|
|
|
test("auto fill right copies", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
autofill(model, "A1", "B1");
|
|
expect(getFieldSync(model, "B1")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("auto fill left copies", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
autofill(model, "B1", "A1");
|
|
expect(getFieldSync(model, "A1")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("copy-paste below", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
copy(model, "B1");
|
|
paste(model, "B2");
|
|
expect(getFieldSync(model, "B2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 1,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("copy-paste up", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B3", "product_uom_qty", 1);
|
|
copy(model, "B3");
|
|
paste(model, "B2");
|
|
paste(model, "B1");
|
|
expect(getFieldSync(model, "B2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 0,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "B1")).toBe(undefined);
|
|
});
|
|
|
|
test("copy-paste horizontally", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
copy(model, "B1");
|
|
paste(model, "A1");
|
|
paste(model, "C1");
|
|
const fieldSync = getFieldSync(model, "B1");
|
|
expect(getFieldSync(model, "A1")).toEqual(fieldSync);
|
|
expect(getFieldSync(model, "C1")).toEqual(fieldSync);
|
|
});
|
|
|
|
test("copy-paste horizontally and vertically", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
copy(model, "B1");
|
|
paste(model, "C2");
|
|
expect(getFieldSync(model, "C2")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 1,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("copy-paste zone", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
addFieldSync(model, "B2", "product_uom_qty", 1);
|
|
copy(model, "B1:B2");
|
|
paste(model, "B3");
|
|
expect(getFieldSync(model, "B3")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 2,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
expect(getFieldSync(model, "B4")).toEqual({
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
indexInList: 3,
|
|
fieldName: "product_uom_qty",
|
|
});
|
|
});
|
|
|
|
test("cut-paste", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "B1", "product_uom_qty", 0);
|
|
const originalFieldSync = getFieldSync(model, "B1");
|
|
cut(model, "B1");
|
|
paste(model, "B3");
|
|
expect(getFieldSync(model, "B3")).toBe(originalFieldSync);
|
|
expect(getFieldSync(model, "B1")).toBe(undefined);
|
|
});
|
|
|
|
test("can't delete main sale order line list", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
const result = model.dispatch("REMOVE_ODOO_LIST", {
|
|
listId: model.getters.getMainSaleOrderLineList().id,
|
|
});
|
|
expect(result.isSuccessful).toBe(false);
|
|
});
|
|
|
|
test("can't delete main sale order global filter", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
const [filter] = model.getters.getGlobalFilters();
|
|
const result = model.dispatch("REMOVE_GLOBAL_FILTER", { id: filter.id });
|
|
expect(result.isSuccessful).toBe(false);
|
|
});
|
|
|
|
test("duplicated field sync error", async () => {
|
|
const { model } = await createSaleOrderSpreadsheetModel();
|
|
addFieldSync(model, "A1", "product_uom_qty", 0);
|
|
addFieldSync(model, "A2", "product_uom_qty", 0);
|
|
expect((await model.getters.getFieldSyncX2ManyCommands()).errors).toEqual([]);
|
|
setCellContent(model, "A1", "111");
|
|
expect((await model.getters.getFieldSyncX2ManyCommands()).errors).toEqual([]);
|
|
setCellContent(model, "A2", "789");
|
|
expect((await model.getters.getFieldSyncX2ManyCommands()).errors).toEqual([
|
|
"Multiple cells are updating the same field of the same record! Unable to determine which one to choose: A1, A2",
|
|
]);
|
|
});
|