Files
enterprise-addons/test_spreadsheet_edition/static/tests/history_action.test.js
T

688 lines
27 KiB
JavaScript

import { expect, test } from "@odoo/hoot";
import { animationFrame, mockTimeZone } from "@odoo/hoot-mock";
import { helpers, registries } from "@odoo/o-spreadsheet";
import { defineTestSpreadsheetEditionModels } from "@test_spreadsheet_edition/../tests/helpers/data";
import { createSpreadsheetTestAction } from "@test_spreadsheet_edition/../tests/helpers/helpers";
import { contains, mockService } from "@web/../tests/web_test_helpers";
import { getSpreadsheetActionModel } from "@spreadsheet_edition/../tests/helpers/webclient_helpers";
import { waitForDataLoaded } from "@spreadsheet/helpers/model";
defineTestSpreadsheetEditionModels();
const { topbarMenuRegistry } = registries;
const revisionSelector = ".o-version-history-wrapper .o-version-history-item";
const panelSelector=".o-version-history";
const uuidGenerator = new helpers.UuidGenerator();
function createRevision(revisions, type, payload) {
const len = revisions.length;
const commands =
type === "REMOTE_REVISION"
? [
{
sheetId: uuidGenerator.smallUuid(),
position: 0,
name: `sheet ${len + 2}`,
type: "CREATE_SHEET",
},
]
: [];
return {
id: len + 1,
name: `revision ${len + 1}`,
serverRevisionId: revisions.at(-1)?.nextRevisionId || "START_REVISION",
nextRevisionId: uuidGenerator.smallUuid(),
version: "1",
timestamp: "2023-09-09 13:00:00",
user: [2, "Superman"],
type,
commands,
...payload,
};
}
/**
*
* @param {SpreadsheetAction} action
* @returns {Model}
*/
function actionModel(action) {
return getSpreadsheetActionModel(action);
}
test("Open history version from the menu", async function () {
const { env } = await createSpreadsheetTestAction("spreadsheet_test_action");
mockService("action", {
doAction(action) {
expect.step(action);
},
});
expect(".o_navbar").toHaveCount(1)
const file = topbarMenuRegistry.getAll().find((item) => item.id === "file");
const showHistory = file.children.find((item) => item.id === "version_history");
await showHistory.execute(env);
expect.verifySteps([
{
type: "ir.actions.client",
tag: "action_open_spreadsheet_history",
params: {
spreadsheet_id: 1,
res_model: "spreadsheet.test",
},
},
]);
});
test("load from the origin value", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
expect.step(`fromSnapshot-${args.args[1]}`);
}
},
});
expect.verifySteps(["fromSnapshot-false"]);
});
test("load action from snapshot", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
expect.step(`fromSnapshot-${args.args[1]}`);
}
},
fromSnapshot: true,
});
expect.verifySteps(["fromSnapshot-true"]);
});
test("load from snapshot when missing revisions", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
expect.step(`fromSnapshot-${args.args[1]}`);
return {
data: {},
name: "test",
revisions: [
createRevision([], "REMOTE_REVISION", {
serverRevisionId: "wrong revision id",
}),
],
};
}
if (args.method === "action_open_spreadsheet") {
expect.step(`editAction-${args.model}`);
return {
type: "ir.actions.client",
tag: "spreadsheet_test_action",
params: {
spreadsheet_id: 1,
},
};
}
},
});
expect.verifySteps(["fromSnapshot-false"]);
let dialog = document.querySelector(".o_dialog");
expect(dialog).not.toBe(null, { message: "Dialog to reload with snapshot opened" });
await contains(dialog.querySelector("button.btn-primary")).click();
expect.verifySteps(["fromSnapshot-true"]);
await animationFrame();
dialog = document.querySelector(".o_dialog");
expect(dialog).not.toBe(null, { message: "Dialog to warn user of corrupted data" });
await contains(dialog.querySelector("button.btn-primary")).click();
expect.verifySteps(["editAction-spreadsheet.test"]);
});
test("Side panel content", async function () {
mockTimeZone(+1);
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(
createRevision(revisions, "REMOTE_REVISION", {
name: "",
})
);
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(
createRevision(revisions, "REMOTE_REVISION", {
user: [3, "Supergirl"],
})
);
return {
data: {},
name: "test",
revisions,
};
}
},
});
expect(revisionSelector).toHaveCount(4, {
message: "3 revisions provided + initial state",
});
// Revision info
expect(`${revisionSelector}:eq(0) .o-sp-badge`).toHaveText("Current");
expect(`${revisionSelector}:eq(1) .o-version-history-timestamp`).toHaveText(
"Sep 9, 2023, 2:00 PM",
{ message: "if the revision has a name" }
);
expect(`${revisionSelector}:eq(2) .o-version-history-timestamp`).toHaveCount(0, {
message: "if the revision has no name",
});
// Revision name
expect(`${revisionSelector}:eq(0) .o-version-history-item-text input`).toHaveValue(
"revision 3",
{ message: "if the revision has a name" }
);
expect(`${revisionSelector}:eq(1) .o-version-history-item-text input`).toHaveValue(
"revision 2",
{ message: "if the revision has a name" }
);
expect(`${revisionSelector}:eq(2) .o-version-history-item-text input`).toHaveValue(
"Sep 9, 2023, 2:00 PM",
{ message: "if the revision does not have a name" }
);
// contributors
expect(`${revisionSelector}:eq(0) .o-version-history-item-text input`).toHaveValue(
"revision 3",
{ message: "if the revision has a name" }
);
expect(`${revisionSelector}:eq(1) .o-version-history-item-text input`).toHaveValue(
"revision 2",
{ message: "if the revision has a name" }
);
expect(`${revisionSelector}:eq(2) .o-version-history-item-text input`).toHaveValue(
"Sep 9, 2023, 2:00 PM",
{ message: "if the revision does not have a name" }
);
});
test("Clicking on initial state resets the data without any revisions", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
}
},
});
expect(actionModel(action).getters.getSheetIds().length).toBe(3);
// rollback to before the first revision. i.e. undo all changes
await contains(`${revisionSelector}:last`).click();
expect(actionModel(action).getters.getSheetIds().length).toBe(1);
});
test("Side panel click loads the old version", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
}
},
});
expect(actionModel(action).getters.getSheetIds().length).toBe(3);
// rollback to the before last revision. i.e. undo a CREATE_SHEET
await contains(`${revisionSelector}:eq(-2)`).click();
expect(actionModel(action).getters.getSheetIds().length).toBe(2);
});
test("Side panel arrow keys navigates in the history", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
}
},
});
expect(actionModel(action).getters.getSheetIds().length).toBe(4);
const target = () => document.querySelector(".o-version-history");
await contains(target()).press("ArrowDown");
expect(actionModel(action).getters.getSheetIds().length).toBe(3);
await contains(target()).press("ArrowDown");
expect(actionModel(action).getters.getSheetIds().length).toBe(2);
await contains(target()).press("ArrowUp");
expect(actionModel(action).getters.getSheetIds().length).toBe(3);
await contains(target()).press("ArrowUp");
expect(actionModel(action).getters.getSheetIds().length).toBe(4);
});
test("Load more revisions", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
for (let i = 0; i < 75; i++) {
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
}
return {
data: {},
name: "test",
revisions,
};
}
},
});
expect(revisionSelector).toHaveCount(50, { message: "the first 50 revisions are loaded" });
const loadMore = document.querySelector(".o-version-history-wrapper .o-version-history-load-more");
expect(loadMore).not.toBe(null, { message: "Load more button is visible" });
await contains(loadMore).click();
expect(revisionSelector).toHaveCount(76, {
message: "the first 50 revisions are loaded + the initial state",
});
});
test("Side panel > make copy", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
switch (args.method) {
case "get_spreadsheet_history":
const revisions = [];
revisions.push(
createRevision(revisions, "REMOTE_REVISION", {
id: 999,
nextRevisionId: "I clicked o",
})
);
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
case "fork_history":
expect(args.kwargs.revision_id).toBe(999);
expect(args.kwargs.spreadsheet_snapshot.revisionId).toBe("I clicked o");
expect.step("forking");
// placeholder return
return {
type: "ir.actions.client",
tag: "reload",
};
default:
break;
}
},
});
await contains(`${revisionSelector}:eq(1)`).click();
await contains(`${revisionSelector}:eq(1) .o-version-history-menu`).click();
const menuItems = document.querySelectorAll(".o-dropdown--menu .o-dropdown-item");
await contains(menuItems[0]).click();
expect.verifySteps(["forking"]);
});
test("Side panel > rename revision", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
return {
data: {},
name: "test",
revisions: [createRevision([], "REMOTE_REVISION")],
};
}
if (args.method === "rename_revision") {
expect.step("renamed");
expect(args.args[0]).toBe(1); // spreadsheet Id
expect(args.args[1]).toBe(1); // revision id
expect(args.args[2]).toBe("test 11");
return true;
}
},
});
const nameInput = document.querySelector(".o-version-history-item-text input");
expect(nameInput).not.toBe(null, { message: "Can rename the revision" });
await contains(nameInput).click();
await contains(nameInput).edit("test 11");
expect.verifySteps(["renamed"]);
});
test("First revision should not be editable", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
return {
data: {},
name: "test",
revisions: [createRevision([], "REMOTE_REVISION")],
};
}
},
});
const historyItems = document.querySelectorAll(".o-version-history-item-text");
expect(historyItems).toHaveCount(2); // One should be an input, the other a span
expect(historyItems[0].querySelector("input")).toHaveCount(1);
expect(historyItems[1].querySelector("span")).toHaveCount(1);
expect(historyItems[0].querySelector("span")).toBe(null);
expect(historyItems[1].querySelector("input")).toBe(null);
});
test("Side panel > restore revision and confirm", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
switch (args.method) {
case "get_spreadsheet_history":
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
case "restore_spreadsheet_version":
expect.step("restored");
expect(args.kwargs.revision_id).toBe(1);
// placeholder return
return {
type: "ir.actions.client",
tag: "reload",
};
default:
break;
}
},
});
await contains(`${revisionSelector}:eq(1)`).click();
await contains(`${revisionSelector}:eq(1) .o-version-history-menu`).click();
await contains(".o-dropdown--menu .o-dropdown-item:eq(1)").click();
await contains(".o_dialog .btn-primary").click();
expect.verifySteps(["restored"]);
});
test("Side panel > restore revision and cancel", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
switch (args.method) {
case "get_spreadsheet_history":
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
case "restore_spreadsheet_version":
throw new Error("should not be called");
default:
break;
}
},
});
await contains(`${revisionSelector}:eq(1)`).click();
await contains(`${revisionSelector}:eq(1) .o-version-history-menu`).click();
await contains(".o-dropdown--menu .o-dropdown-item:eq(1)").click();
await contains(".o_dialog footer .btn:eq(2)").click();
expect(".o_dialog").toHaveCount(0);
});
test("Side panel > restore revision but copy instead", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
switch (args.method) {
case "get_spreadsheet_history":
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return {
data: {},
name: "test",
revisions,
};
case "fork_history":
expect.step("forking");
// placeholder return
return {
type: "ir.actions.client",
tag: "reload",
};
case "restore_spreadsheet_version":
throw new Error("should not be called");
default:
break;
}
},
});
await contains(`${revisionSelector}:eq(1)`).click();
await contains(`${revisionSelector}:eq(1) .o-version-history-menu`).click();
await contains(".o-dropdown--menu .o-dropdown-item:eq(1)").click();
await contains(".o_dialog footer .btn:eq(1)").click();
expect.verifySteps(["forking"]);
});
test("closing side panel rolls back to parent action", async function () {
await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
return {
data: {},
name: "test",
revisions: [createRevision([], "REMOTE_REVISION")],
};
}
if (args.method === "action_open_spreadsheet") {
expect.step(`editAction-${args.model}`);
return {
type: "ir.actions.client",
tag: "spreadsheet_test_action",
params: {
spreadsheet_id: 1,
},
};
}
},
});
await contains(".o-version-history-wrapper div.header>div:nth-child(2)").click();
expect.verifySteps(["editAction-spreadsheet.test"]);
});
test("Undo/redo revisions are rolled back", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(
createRevision(revisions, "REMOTE_REVISION", { name: "Create sheet" }),
);
const originalRevId = revisions.at(-1).nextRevisionId;
revisions.push(
createRevision(revisions, "REVISION_UNDONE", {
name: "Undo sheet creation",
commands: undefined,
undoneRevisionId: originalRevId,
}),
);
revisions.push(
createRevision(revisions, "REVISION_REDONE", {
name: "Redo sheet creation",
commands: undefined,
redoneRevisionId: originalRevId,
}),
);
return { data: {}, name: "test", revisions };
}
},
});
expect(actionModel(action).getters.getSheetIds().length).toBe(2);
await contains(panelSelector).press("ArrowDown");
expect(actionModel(action).getters.getSheetIds().length).toBe(1);
await contains(panelSelector).press("ArrowDown");
expect(actionModel(action).getters.getSheetIds().length).toBe(2);
await contains(panelSelector).press("ArrowUp");
expect(actionModel(action).getters.getSheetIds().length).toBe(1);
await contains(panelSelector).press("ArrowUp");
expect(actionModel(action).getters.getSheetIds().length).toBe(2);
});
test("Datasources are re-evaluated if their domain is altered", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(
createRevision(revisions, "REMOTE_REVISION", { name: "Create sheet" }),
);
revisions.push(
createRevision(revisions, "REMOTE_REVISION", {
name: "edit list definition",
commands: [
{
listId: "1",
domain: [["name", "=", "test"]],
type: "UPDATE_ODOO_LIST_DOMAIN",
},
],
}),
);
return {
data: {
version: 16,
sheets: [
{
id: "sh1",
name: "Sheet 1",
cells: {
A1: { content: '=ODOO.LIST(1,1,"name")' },
},
},
],
lists: {
1: {
columns: ["name"],
domain: [["name", "=", "tabouret"]],
model: "partner",
context: {},
orderBy: [],
id: "1",
name: "Pipeline",
fieldMatching: {},
},
},
},
name: "test",
revisions,
};
}
if (args.method === "web_search_read") {
expect.step(JSON.stringify(args.kwargs.domain));
}
},
});
expect(actionModel(action).getters.getListDefinition("1").domain).toEqual([
["name", "=", "test"],
]);
await contains(panelSelector).press("ArrowDown");
await waitForDataLoaded(actionModel(action));
expect(actionModel(action).getters.getListDefinition("1").domain).toEqual([
["name", "=", "tabouret"],
]);
await contains(panelSelector).press("ArrowUp");
await waitForDataLoaded(actionModel(action));
expect(actionModel(action).getters.getListDefinition("1").domain).toEqual([
["name", "=", "test"],
]);
expect.verifySteps([
`[["name","=","test"]]`,
`[["name","=","tabouret"]]`,
`[["name","=","test"]]`,
]);
});
test("Selection and scroll are preserved when switching revision", async function () {
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return { data: {}, name: "test", revisions };
}
},
});
let model = actionModel(action);
const sheetIds = model.getters.getSheetIds();
model.dispatch("ACTIVATE_SHEET", {
sheetIdFrom: model.getters.getActiveSheetId(),
sheetIdTo: sheetIds[2],
});
model.selection.selectCell(5, 5);
model.dispatch("SET_VIEWPORT_OFFSET", { offsetX: 30, offsetY: 30 });
await animationFrame();
// rollback to the before last revision. i.e. undo a CREATE_SHEET in the first spot
// -> the first sheet is deleted
await contains(`${revisionSelector}:eq(1)`).click();
await animationFrame();
await animationFrame();
model = actionModel(action);
expect(model.getters.getActivePosition()).toEqual({ sheetId: sheetIds[2], row: 5, col: 5 });
expect(model.getters.getActiveSheetScrollInfo()).toEqual({ scrollX: 30, scrollY: 30 });
});
test("Default currency is provided to the model", async function () {
const default_currency = {
id: 1,
name: "Euro",
symbol: "€",
position: "after",
decimalPlaces: 2,
};
const { action } = await createSpreadsheetTestAction("action_open_spreadsheet_history", {
mockRPC: async function (route, args) {
if (args.method === "get_spreadsheet_history") {
const revisions = [];
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
revisions.push(createRevision(revisions, "REMOTE_REVISION"));
return { data: {}, name: "test", revisions, default_currency };
}
if (args.method === "get_company_currency_for_spreadsheet") {
expect.step("get_default_currency");
}
},
});
const model = actionModel(action);
expect(model.getters.getCompanyCurrencyFormat()).toEqual("#,##0.00[$€]");
expect.verifySteps([]);
});