From c5a40e32a3f14f19566f7a9a2110f6f75d784982 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Wed, 14 Oct 2026 12:57:21 +0200 Subject: [PATCH] v1.2.7 --- client/package.json | 2 +- .../editors/BlocksPickerDialog/BlockList.tsx | 7 +- .../BlocksPickerDialog.test.tsx | 105 ++++++++++++++++++ .../BlocksPickerDialog/BlocksPickerDialog.tsx | 6 +- .../editors/QuoteDetailsCardEditor.tsx | 2 + .../{index-D-7KDdEK.js => index-BG_gEWso.js} | 36 +++--- .../{index-AMb79QFB.js => index-zvH6W8yt.js} | 6 +- dist/client/index.html | 2 +- dist/client/version.json | 2 +- package.json | 2 +- server/package.json | 2 +- shared/package.json | 2 +- 12 files changed, 141 insertions(+), 33 deletions(-) create mode 100644 client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.test.tsx rename dist/client/assets/{index-D-7KDdEK.js => index-BG_gEWso.js} (50%) rename dist/client/assets/{index-AMb79QFB.js => index-zvH6W8yt.js} (99%) diff --git a/client/package.json b/client/package.json index 6f537ed..e6184a9 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "@uecko-presupuestador/client", "private": true, - "version": "1.2.6", + "version": "1.2.7", "author": "Rodax Software ", "type": "module", "scripts": { diff --git a/client/src/app/quotes/components/editors/BlocksPickerDialog/BlockList.tsx b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlockList.tsx index 9cb3162..92d8ef2 100644 --- a/client/src/app/quotes/components/editors/BlocksPickerDialog/BlockList.tsx +++ b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlockList.tsx @@ -1,5 +1,4 @@ import { ScrollArea } from "@/ui"; -import { useTranslation } from "react-i18next"; import esBlocks from "./locales/es.json"; import enBlocks from "./locales/en.json"; import frBlocks from "./locales/fr.json"; @@ -15,12 +14,12 @@ const blockData: Record = { es: esBlocks, en: enBlocks, export const BlockList = ({ onSelect, + languageCode, }: { + languageCode: string; onSelect: (data: BlockDataType, quantity: number) => void; }) => { - const { i18n } = useTranslation(); - const language = i18n.resolvedLanguage || "es"; - const listState = blockData[language.toLowerCase()] ?? blockData.es; + const listState = blockData[languageCode.toLowerCase()] ?? blockData.es; return ( diff --git a/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.test.tsx b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.test.tsx new file mode 100644 index 0000000..6e81942 --- /dev/null +++ b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.test.tsx @@ -0,0 +1,105 @@ +import type { ReactNode } from "react"; +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { useMediaQuery } from "@/lib/hooks"; +import { useTranslation } from "react-i18next"; +import { BlocksPickerDialog } from "./BlocksPickerDialog"; +import esBlocks from "./locales/es.json"; +import enBlocks from "./locales/en.json"; +import frBlocks from "./locales/fr.json"; + +globalThis.IS_REACT_ACT_ENVIRONMENT = true; + +jest.mock("@/lib/hooks", () => ({ useMediaQuery: jest.fn() })); +jest.mock("react-i18next", () => ({ useTranslation: jest.fn() })); +jest.mock("i18next", () => ({ t: (key: string) => key })); +jest.mock("@/ui", () => { + const Container = ({ children }: { children: ReactNode }) =>
{children}
; + return { + Button: Container, + Dialog: Container, + DialogContent: Container, + DialogDescription: Container, + DialogFooter: Container, + DialogHeader: Container, + DialogTitle: Container, + Drawer: Container, + DrawerClose: Container, + DrawerContent: Container, + DrawerDescription: Container, + DrawerFooter: Container, + DrawerHeader: Container, + DrawerTitle: Container, + ScrollArea: Container, + }; +}); + +describe.each([true, false])("text block picker (desktop: %s)", (isDesktop) => { + let container: HTMLDivElement; + let root: ReturnType; + + beforeEach(() => { + jest.mocked(useMediaQuery).mockReturnValue(isDesktop); + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + }); + + it.each([ + { languageCode: "es", interfaceLanguage: "en", blocks: esBlocks }, + { languageCode: "en", interfaceLanguage: "es", blocks: enBlocks }, + { languageCode: "FR", interfaceLanguage: "es", blocks: frBlocks }, + ])("displays and selects document text in $languageCode with the interface in $interfaceLanguage", ({ + languageCode, interfaceLanguage, blocks, + }) => { + jest.mocked(useTranslation).mockReturnValue({ + i18n: { resolvedLanguage: interfaceLanguage }, + } as unknown as ReturnType); + const onSelect = jest.fn(); + act(() => { + root.render( + + ); + }); + + const expectedBlocks = blocks.filter(({ title, body }) => title || body); + const buttons = Array.from(container.querySelectorAll("button")); + expect(buttons).toHaveLength(expectedBlocks.length); + expect(buttons.map((button) => button.querySelector(".font-semibold")?.textContent).sort()) + .toEqual(expectedBlocks.map(({ title }) => title).sort()); + const selectedBlock = expectedBlocks[0]; + const selectedButton = buttons.find((button) => + button.querySelector(".font-semibold")?.textContent === selectedBlock.title + ); + expect(selectedButton).toBeDefined(); + act(() => selectedButton!.click()); + + // The insertion handler receives the full localized title and body, including line breaks. + expect(onSelect).toHaveBeenCalledWith(selectedBlock, 1); + expect(container.textContent).toContain("quotes.blocks_picker_dialog.title"); + }); + + it("updates available blocks when the document language changes while open", () => { + const onSelect = jest.fn(); + const props = { isOpen: true, onOpenChange: jest.fn(), onSelect }; + act(() => root.render()); + act(() => root.render()); + + const selectedButton = Array.from(container.querySelectorAll("button")).find((button) => + button.querySelector(".font-semibold")?.textContent === frBlocks[0].title + ); + expect(selectedButton).toBeDefined(); + act(() => selectedButton!.click()); + expect(onSelect).toHaveBeenCalledWith(frBlocks[0], 1); + }); +}); diff --git a/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.tsx b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.tsx index 93a7f0b..b6959f6 100644 --- a/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.tsx +++ b/client/src/app/quotes/components/editors/BlocksPickerDialog/BlocksPickerDialog.tsx @@ -23,7 +23,9 @@ export const BlocksPickerDialog = ({ isOpen, onOpenChange, onSelect, + languageCode, }: { + languageCode: string; isOpen: boolean; onOpenChange: (open: boolean) => void; onSelect: (data: unknown) => void; @@ -39,7 +41,7 @@ export const BlocksPickerDialog = ({ {t("quotes.blocks_picker_dialog.description")} - +