import { DocumentsDocument, defineDocumentSpreadsheetModels, getBasicData, getBasicServerData, } from "@documents_spreadsheet/../tests/helpers/data"; import { createSpreadsheet } from "@documents_spreadsheet/../tests/helpers/spreadsheet_test_utils"; import { describe, expect, getFixture, test } from "@odoo/hoot"; import { dblclick } from "@odoo/hoot-dom"; import { animationFrame } from "@odoo/hoot-mock"; import * as spreadsheet from "@odoo/o-spreadsheet"; import { selectCell, setCellContent, setSelection } from "@spreadsheet/../tests/helpers/commands"; import { getCell, getCellValue } from "@spreadsheet/../tests/helpers/getters"; import { makeDocumentsSpreadsheetMockEnv } from "@documents_spreadsheet/../tests/helpers/model"; import { doMenuAction } from "@spreadsheet/../tests/helpers/ui"; import { prepareWebClientForSpreadsheet } from "@spreadsheet_edition/../tests/helpers/webclient_helpers"; import { contains, getService, mountWithCleanup, patchWithCleanup, onRpc, } from "@web/../tests/web_test_helpers"; import { downloadFile } from "@web/core/network/download"; import { WebClient } from "@web/webclient/webclient"; import { TEST_LOCALES } from "@spreadsheet/../tests/helpers/locale"; const { topbarMenuRegistry } = spreadsheet.registries; const { toZone } = spreadsheet.helpers; const { Model } = spreadsheet; defineDocumentSpreadsheetModels(); describe.current.tags("desktop"); /** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */ test("open spreadsheet with deprecated `active_id` params", async function () { await prepareWebClientForSpreadsheet(); onRpc("/spreadsheet/data/documents.document/1", () => expect.step("spreadsheet-loaded")); await makeDocumentsSpreadsheetMockEnv({ serverData: { models: getBasicData() }, }); await mountWithCleanup(WebClient); await getService("action").doAction({ type: "ir.actions.client", tag: "action_open_spreadsheet", params: { active_id: 1, }, }); expect(".o-spreadsheet").toHaveCount(1, { message: "It should have opened the spreadsheet", }); expect.verifySteps(["spreadsheet-loaded"]); }); test("breadcrumb is rendered the navbar", async function () { const actions = { 2: { id: 2, name: "Documents", res_model: "documents.document", type: "ir.actions.act_window", views: [[false, "list"]], }, }; const views = { "documents.document,false,list": '', }; const serverData = { actions, models: getBasicData(), views }; await prepareWebClientForSpreadsheet(); await makeDocumentsSpreadsheetMockEnv({ serverData, }); await mountWithCleanup(WebClient); await getService("action").doAction(2); await getService("action").doAction({ type: "ir.actions.client", tag: "action_open_spreadsheet", params: { spreadsheet_id: 2, }, }); expect(".o_navbar .o-sp-breadcrumb").toHaveText("Documents", { message: "It should display the breadcrumb", }); expect(".o_navbar .o_sp_name input").toHaveValue("My spreadsheet", { message: "It should display the spreadsheet title", }); expect(".o_navbar .o-sp-favorite").toHaveCount(1, { message: "It should display the favorite toggle button", }); }); test("Can open a spreadsheet in readonly", async function () { onRpc("/spreadsheet/data/documents.document/*", () => ({ data: {}, name: "name", revisions: [], isReadonly: true, })); const { model } = await createSpreadsheet(); expect(model.getters.isReadonly()).toBe(true); }); test("format menu with default currency", async function () { onRpc("/spreadsheet/data/documents.document/*", () => ({ data: {}, name: "name", revisions: [], default_currency: { code: "θdoo", symbol: "θ", position: "after", decimalPlaces: 2, }, })); const { model, env } = await createSpreadsheet(); await doMenuAction( topbarMenuRegistry, ["format", "format_number", "format_number_currency"], env ); expect(getCell(model, "A1").format).toBe("#,##0.00[$θ]"); await doMenuAction( topbarMenuRegistry, ["format", "format_number", "format_number_currency_rounded"], env ); expect(getCell(model, "A1").format).toBe("#,##0[$θ]"); }); test("dialog window not normally displayed", async function () { await createSpreadsheet(); expect(".o_dialog").toHaveCount(0, { message: "Dialog should not normally be displayed " }); }); test("notify user window", async function () { const { env } = await createSpreadsheet(); env.notifyUser({ text: "this is a notification", type: "warning", sticky: true }); await animationFrame(); expect(".o_notification:has(.o_notification_bar.bg-warning)").toHaveText( "this is a notification" ); }); test("raise error window", async function () { const { env } = await createSpreadsheet(); env.raiseError("this is a message in an opened window that requests user action"); await animationFrame(); expect(".o_dialog").toHaveCount(1, { message: "Dialog can be opened" }); expect(".modal-body p").toHaveText( "this is a message in an opened window that requests user action", { message: "Can set dialog content" } ); expect(document.querySelector(".o_dialog_error_text")).toBe(null, { message: "NotifyUser have no error text", }); expect(document.querySelectorAll(".modal-footer button").length).toBe(1, { message: "NotifyUser have 1 button", }); }); test("ask confirmation when merging", async function () { const { model } = await createSpreadsheet(); const sheetId = model.getters.getActiveSheetId(); setCellContent(model, "A2", "hello"); setSelection(model, "A1:A2"); await animationFrame(); await contains(".o-menu-item-button[title='Merge cells']").click(); expect(".o_dialog").toHaveCount(1, { message: "Dialog should be open" }); await contains(".o_dialog .btn-secondary").click(); // cancel expect(model.getters.isSingleCellOrMerge(sheetId, toZone("A1:A2"))).toBe(false); await contains(".o-menu-item-button[title='Merge cells']").click(); await contains(".o_dialog .btn-primary").click(); // confirm expect(model.getters.isSingleCellOrMerge(sheetId, toZone("A1:A2"))).toBe(true); }); test("Cancel callback of askConfirmation is called when using the cancel button", async function () { const { env } = await createSpreadsheet(); env.askConfirmation( "Dialog title", () => {}, () => { expect.step("cancel callback"); } ); await animationFrame(); expect(".o_dialog .btn-primary").toHaveText("Yes"); expect(".o_dialog .btn-secondary").toHaveText("No"); await contains(".o_dialog .btn-secondary").click(); expect.verifySteps(["cancel callback"]); }); test("Grid has still the focus after a dialog", async function () { const { model, env } = await createSpreadsheet(); selectCell(model, "F4"); env.raiseError("Notification"); await animationFrame(); await contains(".modal-footer .btn-primary").click(); await animationFrame(); expect(".o-grid div.o-composer:first").toBeFocused(); }); test("menu > download as json", async function () { let downloadedData = null; patchWithCleanup(downloadFile, { _download: (data, fileName) => { expect.step("download"); expect(data).toInclude("Hello World"); expect(data).toInclude("A3"); expect(fileName).toBe("My spreadsheet.osheet.json"); downloadedData = data; }, }); const serverData = getBasicServerData(); const spreadsheet = DocumentsDocument._records[1]; spreadsheet.name = "My spreadsheet"; spreadsheet.spreadsheet_data = JSON.stringify({ sheets: [{ cells: { A3: "Hello World" } }], }); const { env, model } = await createSpreadsheet({ spreadsheetId: spreadsheet.id, serverData, }); expect(getCellValue(model, "A3")).toBe("Hello World"); await doMenuAction(topbarMenuRegistry, ["file", "download_as_json"], env); expect.verifySteps(["download"]); const modelFromLoadedJSON = new Model(JSON.parse(downloadedData)); expect(getCellValue(modelFromLoadedJSON, "A3")).toBe("Hello World"); }); test("menu > copy", async function () { const serverData = getBasicServerData(); const spreadsheet = DocumentsDocument._records[1]; spreadsheet.name = "My spreadsheet"; spreadsheet.spreadsheet_data = JSON.stringify({ sheets: [{ cells: { A3: "Hello World" } }], }); const { env, model } = await createSpreadsheet({ spreadsheetId: spreadsheet.id, serverData, mockRPC: function (_, { method, args, kwargs }) { if (method === "copy") { expect.step("copy"); expect(args[0]).toEqual([2]); expect(kwargs).toInclude("default"); } }, }); expect(getCellValue(model, "A3")).toBe("Hello World"); await doMenuAction(topbarMenuRegistry, ["file", "make_copy"], env); expect.verifySteps(["copy"]); }); test("Spreadsheet is created with locale in data", async function () { const serverData = getBasicServerData(); serverData.models["documents.document"] = { records: [ { id: 3000, name: "My template spreadsheet", spreadsheet_data: JSON.stringify({ settings: { locale: TEST_LOCALES.fr_FR } }), }, ], }; const { model } = await createSpreadsheet({ serverData, spreadsheetId: 3000 }); expect(model.getters.getLocale().code).toBe("fr_FR"); }); test("Odoo locales are displayed in setting side panel", async function () { const { env } = await createSpreadsheet({ mockRPC: function (route, { method, model }) { if (method === "get_locales_for_spreadsheet") { return Object.values(TEST_LOCALES); } }, }); env.openSidePanel("Settings", {}); await animationFrame(); const loadedLocales = []; const options = document.querySelectorAll(".o-settings-panel select option"); for (const option of options) { loadedLocales.push(option.value); } expect(loadedLocales).toEqual(["en_US", "fr_FR", "od_OO"]); }); test("sheetName should not be left empty", async function () { const fixture = getFixture(); await createSpreadsheet(); await dblclick(".o-sheet-list .o-sheet-name"); await animationFrame(); expect(".o-sheet-name-editable").toHaveCount(1); fixture.querySelector(".o-sheet-list .o-sheet-name").innerText = ""; await contains(".o-sheet-list .o-sheet-name").press("Enter"); fixture.querySelector(".modal-dialog"); expect(".modal-dialog").toBeVisible({ message: "dialog should be visible" }); await contains(".modal-dialog .btn-primary").click(); expect(".o-sheet-name-editable").toHaveCount(1); }); test("Frozen spreadsheet should restrict copy", async function () { onRpc("/spreadsheet/data/documents.document/*", () => ({ data: {}, name: "name", revisions: [], handler: "frozen_spreadsheet", isReadonly: false, })); const { model } = await createSpreadsheet(); expect(model.canDispatch("COPY", {}).isSuccessful).toBe(false); });