/* @odoo-module */ import * as spreadsheet from "@odoo/o-spreadsheet"; import { downloadFile } from "@web/core/network/download"; import { getFixture, nextTick, click, patchWithCleanup, triggerEvent, } from "@web/../tests/helpers/utils"; import { contains } from "@web/../tests/utils"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; import { getBasicData, getBasicServerData } from "@spreadsheet/../tests/utils/data"; import { prepareWebClientForSpreadsheet } from "@spreadsheet_edition/../tests/utils/webclient_helpers"; import { createSpreadsheet } from "../spreadsheet_test_utils"; import { setCellContent, selectCell, setSelection } from "@spreadsheet/../tests/utils/commands"; import { doMenuAction } from "@spreadsheet/../tests/utils/ui"; import { getCell, getCellContent, getCellValue } from "@spreadsheet/../tests/utils/getters"; const { topbarMenuRegistry } = spreadsheet.registries; const { toZone } = spreadsheet.helpers; const { Model } = spreadsheet; /** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */ let target; export const TEST_LOCALES = [ { name: "United States", code: "en_US", thousandsSeparator: ",", decimalSeparator: ".", dateFormat: "m/d/yyyy", timeFormat: "hh:mm:ss a", formulaArgSeparator: ",", }, { name: "France", code: "fr_FR", thousandsSeparator: " ", decimalSeparator: ",", dateFormat: "dd/mm/yyyy", timeFormat: "hh:mm:ss", formulaArgSeparator: ";", }, { name: "Odooland", code: "od_OO", thousandsSeparator: "*", decimalSeparator: ".", dateFormat: "yyyy/mm/dd", timeFormat: "hh:mm:ss", formulaArgSeparator: ",", }, ]; QUnit.module( "documents_spreadsheet > Spreadsheet Client Action", { beforeEach: function () { target = getFixture(); }, }, function () { QUnit.test("open spreadsheet with deprecated `active_id` params", async function (assert) { assert.expect(4); await prepareWebClientForSpreadsheet(); const webClient = await createWebClient({ serverData: { models: getBasicData() }, mockRPC: async function (route, args) { if (args.method === "join_spreadsheet_session") { assert.step("spreadsheet-loaded"); assert.equal(args.args[0], 1, "It should load the correct spreadsheet"); } }, }); await doAction(webClient, { type: "ir.actions.client", tag: "action_open_spreadsheet", params: { active_id: 1, }, }); assert.containsOnce(target, ".o-spreadsheet", "It should have opened the spreadsheet"); assert.verifySteps(["spreadsheet-loaded"]); }); QUnit.test("breadcrumb is rendered in control panel", async function (assert) { assert.expect(3); const actions = { 1: { id: 1, name: "Documents", res_model: "documents.document", type: "ir.actions.act_window", views: [[false, "list"]], }, }; const views = { "documents.document,false,list": '', "documents.document,false,search": "", }; const serverData = { actions, models: getBasicData(), views }; await prepareWebClientForSpreadsheet(); const webClient = await createWebClient({ serverData, }); await doAction(webClient, 1); await doAction(webClient, { type: "ir.actions.client", tag: "action_open_spreadsheet", params: { spreadsheet_id: 1, }, }); assert.strictEqual( target.querySelector("ol.breadcrumb").textContent, "Documents", "It should display the breadcrumb" ); assert.strictEqual( target.querySelector(".o_breadcrumb input").value, "My spreadsheet", "It should display the spreadsheet title" ); assert.containsOnce( target, ".o_breadcrumb .o_spreadsheet_favorite", "It should display the favorite toggle button" ); }); QUnit.test("Can open a spreadsheet in readonly", async function (assert) { const { model } = await createSpreadsheet({ mockRPC: async function (route, args) { if (args.method === "join_spreadsheet_session") { return { data: {}, name: "name", revisions: [], isReadonly: true, }; } }, }); assert.ok(model.getters.isReadonly()); }); QUnit.test("format menu with default currency", async function (assert) { const { model, env } = await createSpreadsheet({ mockRPC: async function (route, args) { if (args.method === "join_spreadsheet_session") { return { data: {}, name: "name", revisions: [], default_currency: { code: "θdoo", symbol: "θ", position: "after", decimalPlaces: 2, }, }; } }, }); await doMenuAction( topbarMenuRegistry, ["format", "format_number", "format_number_currency"], env ); assert.equal(getCell(model, "A1").format, "#,##0.00[$θ]"); await doMenuAction( topbarMenuRegistry, ["format", "format_number", "format_number_currency_rounded"], env ); assert.equal(getCell(model, "A1").format, "#,##0[$θ]"); }); QUnit.test("dialog window not normally displayed", async function (assert) { assert.expect(1); await createSpreadsheet(); const dialog = document.querySelector(".o_dialog"); assert.equal(dialog, undefined, "Dialog should not normally be displayed "); }); QUnit.test("notify user window", async function () { const { env } = await createSpreadsheet(); env.notifyUser({ text: "this is a notification", type: "warning", sticky: true }); await contains(".o_notification.border-warning", { text: "this is a notification" }); }); QUnit.test("raise error window", async function (assert) { assert.expect(4); const { env } = await createSpreadsheet(); env.raiseError("this is a message in an opened window that requests user action"); await nextTick(); const dialog = document.querySelector(".o_dialog"); assert.ok(dialog !== undefined, "Dialog can be opened"); assert.equal( document.querySelector(".modal-body p").textContent, "this is a message in an opened window that requests user action", "Can set dialog content" ); assert.equal( document.querySelector(".o_dialog_error_text"), null, "NotifyUser have no error text" ); assert.equal( document.querySelectorAll(".modal-footer button").length, 1, "NotifyUser have 1 button" ); }); QUnit.test("ask confirmation when merging", async function (assert) { const fixture = getFixture(); const { model } = await createSpreadsheet(); const sheetId = model.getters.getActiveSheetId(); setCellContent(model, "A2", "hello"); setSelection(model, "A1:A2"); await nextTick(); await click(fixture, ".o-menu-item-button[title='Merge cells']"); const dialog = document.querySelector(".o_dialog"); assert.ok(dialog, "Dialog should be open"); await click(document, ".o_dialog .btn-secondary"); // cancel assert.equal(model.getters.isSingleCellOrMerge(sheetId, toZone("A1:A2")), false); await click(fixture, ".o-menu-item-button[title='Merge cells']"); await click(document, ".o_dialog .btn-primary"); // confirm assert.equal(model.getters.isSingleCellOrMerge(sheetId, toZone("A1:A2")), true); }); QUnit.test("Grid has still the focus after a dialog", async function (assert) { const { model, env } = await createSpreadsheet(); selectCell(model, "F4"); env.raiseError("Notification"); await nextTick(); await click(document, ".modal-footer .btn-primary"); await nextTick(); assert.strictEqual(document.activeElement, document.querySelector(".o-grid div.o-composer")); }); QUnit.test("convert data from template", async function (assert) { const data = { sheets: [ { id: "sheet1", cells: { A1: { content: '=ODOO.PIVOT(1,"probability","foo", ODOO.PIVOT.POSITION(1, "foo", 1))', }, }, }, ], pivots: { 1: { id: 1, colGroupBys: ["foo"], domain: [], measures: [{ field: "probability" }], model: "partner", rowGroupBys: ["bar"], context: {}, }, }, }; const serverData = getBasicServerData(); serverData.models["documents.document"].records.push({ id: 3000, name: "My template spreadsheet", spreadsheet_data: JSON.stringify(data), }); const { model } = await createSpreadsheet({ spreadsheetId: 3000, serverData, convert_from_template: true, mockRPC: function (route, { method, model, args }) { if (model === "documents.document" && method === "write") { assert.step("reset data"); const data = JSON.parse(args[1].spreadsheet_data); assert.deepEqual( data.sheets[0].cells.A1.content, '=ODOO.PIVOT(1,"probability","foo","1")' ); } }, }); assert.strictEqual( getCellContent(model, "A1"), '=ODOO.PIVOT(1,"probability","foo","1")' ); await nextTick(); assert.verifySteps(["reset data"]); }); QUnit.test("menu > download as json", async function (assert) { let downloadedData = null; patchWithCleanup(downloadFile, { _download: (data, fileName) => { assert.step("download"); assert.ok(data.includes("Hello World")); assert.ok(data.includes("A3")); assert.strictEqual(fileName, "My spreadsheet.osheet.json"); downloadedData = data; }, }); const serverData = getBasicServerData(); const spreadsheet = serverData.models["documents.document"].records[1]; spreadsheet.name = "My spreadsheet"; spreadsheet.spreadsheet_data = JSON.stringify({ sheets: [{ cells: { A3: { content: "Hello World" } } }], }); const { env, model } = await createSpreadsheet({ spreadsheetId: spreadsheet.id, serverData, }); assert.strictEqual(getCellValue(model, "A3"), "Hello World"); await doMenuAction(topbarMenuRegistry, ["file", "download_as_json"], env); assert.verifySteps(["download"]); const modelFromLoadedJSON = new Model(JSON.parse(downloadedData)); assert.strictEqual(getCellValue(modelFromLoadedJSON, "A3"), "Hello World"); }); QUnit.test("Spreadsheet is created with locale in data", async function (assert) { const serverData = getBasicServerData(); serverData.models["documents.document"].records.push({ id: 3000, name: "My template spreadsheet", spreadsheet_data: JSON.stringify({ settings: { locale: TEST_LOCALES[1] } }), }); const { model } = await createSpreadsheet({ serverData, spreadsheetId: 3000 }); assert.deepEqual(model.getters.getLocale().code, "fr_FR"); }); QUnit.test("Odoo locales are displayed in setting side panel", async function (assert) { const { env } = await createSpreadsheet({ mockRPC: function (route, { method, model }) { if (method === "get_locales_for_spreadsheet") { return TEST_LOCALES; } }, }); env.openSidePanel("Settings", {}); await nextTick(); const loadedLocales = []; const options = document.querySelectorAll(".o-settings-panel select option"); for (const option of options) { loadedLocales.push(option.value); } assert.deepEqual(loadedLocales, ["en_US", "fr_FR", "od_OO"]); }); QUnit.test("sheetName should not be left empty", async function (assert) { const fixture = getFixture(); await createSpreadsheet(); const sheetName = fixture.querySelector(".o-sheet-list .o-sheet-name"); await triggerEvent(fixture, ".o-sheet-list .o-sheet-name", "dblclick"); await nextTick(); assert.ok(fixture.querySelector(".o-sheet-name-editable")); sheetName.innerText = ""; await triggerEvent(fixture, ".o-sheet-list .o-sheet-name", "keydown", { key: "Enter" }); await nextTick(); const dialog = document.querySelector(".o_dialog"); assert.ok(dialog, "dialog should be visible"); await click(document, ".o_dialog .btn-primary"); assert.ok(fixture.querySelector(".o-sheet-name-editable")); }); } );