diff --git a/src/components/composer/standalone_composer/standalone_composer_store.ts b/src/components/composer/standalone_composer/standalone_composer_store.ts index 8218aefd95..c423a25f45 100644 --- a/src/components/composer/standalone_composer/standalone_composer_store.ts +++ b/src/components/composer/standalone_composer/standalone_composer_store.ts @@ -1,4 +1,5 @@ import { rangeTokenize } from "../../../formulas"; +import { localizeContent } from "../../../helpers/locale"; import { AutoCompleteProviderDefinition } from "../../../registries"; import { Get } from "../../../store_engine"; import { UID } from "../../../types"; @@ -31,12 +32,13 @@ export class StandaloneComposerStore extends AbstractComposerStore { } protected getComposerContent(): string { + let content = this._currentContent; if (this.editionMode === "inactive") { // References in the content might not be linked to the current active sheet // We here force the sheet name prefix for all references that are not in // the current active sheet const defaultRangeSheetId = this.args().defaultRangeSheetId; - return rangeTokenize(this.args().content) + content = rangeTokenize(this.args().content) .map((token) => { if (token.type === "REFERENCE") { const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value); @@ -46,7 +48,8 @@ export class StandaloneComposerStore extends AbstractComposerStore { }) .join(""); } - return this._currentContent; + + return localizeContent(content, this.getters.getLocale()); } stopEdition() { diff --git a/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.ts b/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.ts index 771383bdf6..57c6ec4f5b 100644 --- a/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.ts +++ b/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.ts @@ -81,4 +81,8 @@ export class PivotMeasureEditor extends Component { measure: this.props.measure, }); } + + get isCalculatedMeasureInvalid(): boolean { + return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression; + } } diff --git a/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.xml b/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.xml index 1dbe022d98..c8168151f4 100644 --- a/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.xml +++ b/src/components/side_panel/pivot/pivot_layout_configurator/pivot_measure/pivot_measure.xml @@ -24,6 +24,7 @@ composerContent="measure.computedBy.formula" defaultRangeSheetId="measure.computedBy.sheetId" contextualAutocomplete="getMeasureAutocomplete()" + invalid="isCalculatedMeasureInvalid" /> diff --git a/tests/composer/standalone_composer_component.test.ts b/tests/composer/standalone_composer_component.test.ts index 740f7894ee..e56ca1fdb3 100644 --- a/tests/composer/standalone_composer_component.test.ts +++ b/tests/composer/standalone_composer_component.test.ts @@ -5,7 +5,8 @@ import { StandaloneComposer } from "../../src/components/composer/standalone_com import { zoneToXc } from "../../src/helpers"; import { sidePanelRegistry } from "../../src/registries/side_panel_registry"; import { Store } from "../../src/store_engine"; -import { createSheet } from "../test_helpers/commands_helpers"; +import { createSheet, updateLocale } from "../test_helpers/commands_helpers"; +import { FR_LOCALE } from "../test_helpers/constants"; import { click, keyDown, simulateClick } from "../test_helpers/dom_helper"; import { editStandaloneComposer, mountSpreadsheet, nextTick } from "../test_helpers/helpers"; @@ -134,4 +135,18 @@ describe("Spreadsheet integrations tests", () => { // to the new confirmed content expect(composerEl.textContent).toBe("content from props"); }); + + test("Standalone composer works with non-default locale", async () => { + updateLocale(model, FR_LOCALE); + await openSidePanelWithComposer("=SUM(1,2.5)"); + expect(composerEl.textContent).toBe("=SUM(1;2,5)"); + + await editStandaloneComposer(composerSelector, " + SUM(1,5;4)", { + fromScratch: false, + confirm: false, + }); + expect(composerEl.textContent).toBe("=SUM(1;2,5) + SUM(1,5;4)"); + await keyDown({ key: "Enter" }); + expect(onConfirm).toHaveBeenCalledWith("=SUM(1,2.5) + SUM(1.5,4)"); + }); }); diff --git a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts index 1c10a13cd9..e9abef05de 100644 --- a/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts +++ b/tests/pivots/spreadsheet_pivot/spreadsheet_pivot_side_panel.test.ts @@ -150,6 +150,14 @@ describe("Spreadsheet pivot side panel", () => { ]); }); + test("Invalid calculated measure formula have an invalid class on the composer", async () => { + await click(fixture.querySelectorAll(".add-dimension")[2]); + expect(fixture.querySelector(".o-popover")).toBeDefined(); + await click(fixture, ".add-calculated-measure"); + await editStandaloneComposer(".pivot-dimension .o-composer", "=abcdefg()"); + expect(fixture.querySelector(".o-standalone-composer")).toHaveClass("o-invalid"); + }); + test("can select a cell in the grid in several sheets", async () => { setCellContent(model, "A1", "amount"); setCellContent(model, "A2", "10"); diff --git a/tests/setup/jest_extend.ts b/tests/setup/jest_extend.ts index 2201136113..1e90cc3350 100644 --- a/tests/setup/jest_extend.ts +++ b/tests/setup/jest_extend.ts @@ -37,6 +37,8 @@ declare global { toHaveValue(value: string | boolean): R; toHaveText(text: string): R; toHaveCount(count: number): R; + toHaveClass(className: string): R; + toHaveAttribute(attribute: string, value: string): R; } } } @@ -258,6 +260,44 @@ CancelledReasons: ${this.utils.printReceived(dispatchResult.reasons)} } return { pass: true, message: () => "" }; }, + toHaveClass(target: DOMTarget, expectedClass: string) { + const element = getTarget(target); + if (!(element instanceof HTMLElement)) { + const message = element ? "Target is not an HTML element" : "Target not found"; + return { pass: false, message: () => message }; + } + const pass = element.classList.contains(expectedClass); + const message = () => + pass + ? "" + : `expect(target).toHaveClass(expected);\n\n${this.utils.printDiffOrStringify( + expectedClass, + element.className, + "Expected class", + "Received class", + false + )}`; + return { pass, message }; + }, + toHaveAttribute(target: DOMTarget, attribute: string, expectedValue: string) { + const element = getTarget(target); + if (!(element instanceof HTMLElement)) { + const message = element ? "Target is not an HTML element" : "Target not found"; + return { pass: false, message: () => message }; + } + const pass = element.getAttribute(attribute) === expectedValue; + const message = () => + pass + ? "" + : `expect(target).toHaveAttribute(attribute, expected);\n\n${this.utils.printDiffOrStringify( + expectedValue, + element.getAttribute(attribute), + "Expected value", + "Received value", + false + )}`; + return { pass, message }; + }, }); function getTarget(target: DOMTarget): Element | Document | Window {