diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.test.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.test.tsx new file mode 100644 index 00000000..58c632f7 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.test.tsx @@ -0,0 +1,42 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { renderToStaticMarkup } from "react-dom/server"; + +import { LineEditor } from "./line-editor"; + +test("asocia el selector de añadir línea al botón de añadir al final", () => { + const html = renderToStaticMarkup( + line} + insertAfterLabel="Insertar después" + insertBeforeLabel="Insertar antes" + lines={[] as string[]} + moveDownLabel="Mover abajo" + moveUpLabel="Mover arriba" + onAddAfter={() => undefined} + onAddAtEnd={() => undefined} + onAddAtStart={() => undefined} + onAddBefore={() => undefined} + onDuplicate={() => undefined} + onMoveDown={() => undefined} + onMoveUp={() => undefined} + onRemove={() => undefined} + removeLabel="Eliminar" + title="Líneas" + /> + ); + + const addAtStartLabelIndex = html.indexOf("Añadir al inicio"); + const addAtEndTestIdIndex = html.indexOf('data-testid="proforma-add-item"'); + const addAtEndLabelIndex = html.indexOf("Añadir al final"); + + assert.ok(addAtStartLabelIndex < addAtEndTestIdIndex); + assert.ok(addAtEndTestIdIndex < addAtEndLabelIndex); +}); diff --git a/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.tsx b/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.tsx index 642e63bd..9b3653ab 100644 --- a/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.tsx +++ b/modules/customer-invoices/src/web/proformas/update/ui/blocks/line-editor.tsx @@ -38,6 +38,7 @@ export interface LineEditorProps { description?: React.ReactNode; lines: TLine[]; getLineKey: (line: TLine, index: number) => React.Key; + getLineTestId?: (line: TLine, index: number) => string; columns: LineEditorColumn[]; @@ -64,6 +65,10 @@ export interface LineEditorProps { moveDownLabel: string; removeLabel: string; + addAtEndTestId?: string; + getLineActionsTestId?: (line: TLine, index: number) => string; + getLineRemoveTestId?: (line: TLine, index: number) => string; + disabled?: boolean; className?: string; } @@ -73,6 +78,7 @@ export const LineEditor = ({ description, lines, getLineKey, + getLineTestId, columns, @@ -99,6 +105,10 @@ export const LineEditor = ({ moveDownLabel, removeLabel, + addAtEndTestId, + getLineActionsTestId, + getLineRemoveTestId, + disabled = false, className, @@ -124,6 +134,7 @@ export const LineEditor = ({ } /> ); diff --git a/tests/e2e/customer-invoices/proformas/proforma-items.spec.ts b/tests/e2e/customer-invoices/proformas/proforma-items.spec.ts new file mode 100644 index 00000000..07866c32 --- /dev/null +++ b/tests/e2e/customer-invoices/proformas/proforma-items.spec.ts @@ -0,0 +1,127 @@ +import type { Page, TestInfo } from "@playwright/test"; +import { expect, test } from "@playwright/test"; + +import { loginViaUi } from "../../support/login-via-ui"; + +import { createProformaViaUi } from "./support/create-proforma-via-ui"; + +const itemRows = (page: Page) => page.getByTestId(/^proforma-item-row-/); + +const saveProforma = async (page: Page, id: string) => { + const responsePromise = page.waitForResponse( + (response) => + response.request().method() === "PUT" && + new URL(response.url()).pathname.endsWith(`/proformas/${id}`) + ); + + await page.getByTestId("proforma-update-submit").click(); + expect((await responsePromise).status()).toBe(200); +}; + +const addItem = async (page: Page, description: string) => { + await page.getByTestId("proforma-add-item").click(); + const row = itemRows(page).last(); + await expect(row).toBeVisible(); + const descriptionInput = row.locator('[name$=".description"]'); + await descriptionInput.fill(description); +}; + +const expectItemDescriptions = async (page: Page, descriptions: string[]) => { + const rows = itemRows(page); + await expect(rows).toHaveCount(descriptions.length); + + for (const [index, description] of descriptions.entries()) { + await expect(rows.nth(index).locator('[name$=".description"]')).toHaveValue(description); + } +}; + +const createProformaWithItemsViaUi = async ( + page: Page, + testInfo: TestInfo, + descriptions: string[] +) => { + const proforma = await createProformaViaUi(page, testInfo); + + for (const description of descriptions) { + await addItem(page, description); + } + + await saveProforma(page, proforma.id); + return proforma; +}; + +test("CUSTOMER-INVOICES-PROFORMA-ITEMS-001: añade una línea a un draft vacío y conserva sus datos", async ({ + page, +}, testInfo) => { + await loginViaUi(page); + const proforma = await createProformaViaUi(page, testInfo); + const description = `E2E ITEM ADD ${proforma.token}`; + + await addItem(page, description); + await saveProforma(page, proforma.id); + await expectItemDescriptions(page, [description]); + await page.reload(); + await expectItemDescriptions(page, [description]); +}); + +test("CUSTOMER-INVOICES-PROFORMA-ITEMS-002: edita una línea persistida sin duplicarla", async ({ + page, +}, testInfo) => { + await loginViaUi(page); + const original = `E2E ITEM ORIGINAL ${testInfo.testId}`; + const proforma = await createProformaWithItemsViaUi(page, testInfo, [original]); + const updated = `E2E ITEM UPDATED ${proforma.token}`; + + const row = itemRows(page).first(); + await row.locator('[name$=".description"]').fill(updated); + await saveProforma(page, proforma.id); + await expectItemDescriptions(page, [updated]); + await page.reload(); + await expectItemDescriptions(page, [updated]); +}); + +test("CUSTOMER-INVOICES-PROFORMA-ITEMS-003: elimina la última línea y conserva el draft vacío", async ({ + page, +}, testInfo) => { + await loginViaUi(page); + const proforma = await createProformaWithItemsViaUi(page, testInfo, [ + `E2E ITEM DELETE ${testInfo.testId}`, + ]); + + const row = itemRows(page).first(); + await row.getByTestId(/^proforma-item-actions-/).click(); + await page.getByTestId(/^proforma-item-delete-/).click(); + await expect(itemRows(page)).toHaveCount(0); + + const requestPromise = page.waitForRequest( + (request) => + request.method() === "PUT" && + new URL(request.url()).pathname.endsWith(`/proformas/${proforma.id}`) + ); + await saveProforma(page, proforma.id); + expect((await requestPromise).postDataJSON()).toMatchObject({ items: [] }); + await page.reload(); + await expect(itemRows(page)).toHaveCount(0); + await expect(page.locator('input[name="reference"]')).toBeVisible(); +}); + +test("CUSTOMER-INVOICES-PROFORMA-ITEMS-004: conserva varias líneas y su orden tras recargar", async ({ + page, +}, testInfo) => { + await loginViaUi(page); + const proforma = await createProformaViaUi(page, testInfo); + const descriptions = [ + `E2E ITEM AAA ${proforma.token}`, + `E2E ITEM BBB ${proforma.token}`, + `E2E ITEM CCC ${proforma.token}`, + ]; + + for (const description of descriptions) { + await addItem(page, description); + } + + await saveProforma(page, proforma.id); + await expectItemDescriptions(page, descriptions); + await page.reload(); + await expectItemDescriptions(page, descriptions); +});