diff --git a/.env.development b/.env.development index e57b351..3554da3 100644 --- a/.env.development +++ b/.env.development @@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_USE_CGI=false # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.882 +NEXT_PUBLIC_APP_VERSION=1.6.883 NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) diff --git a/.env.production b/.env.production index 579a671..f1e4472 100644 --- a/.env.production +++ b/.env.production @@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_USE_CGI=true # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.882 +NEXT_PUBLIC_APP_VERSION=1.6.883 NEXT_PUBLIC_CPL_MODE=production \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ab59554..8c67f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.6.883] – 2025-09-09 + +- style: TDR + +--- ## [1.6.882] – 2025-09-09 - style: dark mode ISO, RSL und TDR diff --git a/package-lock.json b/package-lock.json index 608b851..17ef44d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cpl-v4", - "version": "1.6.882", + "version": "1.6.883", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cpl-v4", - "version": "1.6.882", + "version": "1.6.883", "dependencies": { "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", diff --git a/package.json b/package.json index ea37e28..c1ab2dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cpl-v4", - "version": "1.6.882", + "version": "1.6.883", "private": true, "scripts": { "dev": "next dev -p 3000", diff --git a/playwright/tests/components/main/kabelueberwachung/isoModal.test.ts b/playwright/tests/components/main/kabelueberwachung/isoModal.test.ts new file mode 100644 index 0000000..141e711 --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/isoModal.test.ts @@ -0,0 +1,89 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * ISO Modal UI / Behavior Regression Test + * Covers: + * - Opening ISO modal from cable monitoring page + * - Verifies modal structure (header, fullscreen + close buttons) + * - Dropdown (Messkurve <-> Meldungen) presence & options + * - Toolbar elements (KÜ badge, DateRangePicker fields, Mode dropdown, Daten laden button) + * - Style smoke checks via class attributes (non brittle, only key tokens) + * - Switching to Meldungen hides date + mode controls and shows table headers + */ +test.describe("ISO Modal", () => { + test("opens and validates core UI + view toggle behavior", async ({ + page, + }) => { + await page.goto("/kabelueberwachung"); + + const firstChartButton = page + .locator(".bg-littwin-blue.text-white") + .first(); + await highlightAndExpectVisible(page, firstChartButton); + await firstChartButton.click(); + + const heading = page.getByRole("heading", { name: "Isolationswiderstand" }); + await highlightAndExpectVisible(page, heading); + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + await expect( + dialog + .locator("button:has(i.bi-fullscreen-exit, i.bi-arrows-fullscreen)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + const viewToggle = dialog.getByRole("button", { + name: /Messkurve|Meldungen/, + }); + await highlightAndExpectVisible(page, viewToggle); + + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + await expect(dialog.getByText("Von")).toBeVisible(); + await expect(dialog.getByText("Bis")).toBeVisible(); + + const modeBtn = dialog.getByRole("button", { + name: /Alle Messwerte|Stündlich|Täglich/, + }); + await modeBtn.click(); + await expect( + page.getByRole("option", { name: /Alle Messwerte/ }) + ).toBeVisible(); + await expect(page.getByRole("option", { name: /Stündlich/ })).toBeVisible(); + await expect(page.getByRole("option", { name: /Täglich/ })).toBeVisible(); + await page.getByRole("option", { name: "Stündlich" }).click(); + await modeBtn.click(); + await page.getByRole("option", { name: "Alle Messwerte" }).click(); + + const loadBtn = dialog.getByRole("button", { name: "Daten laden" }); + await expect(loadBtn).toBeVisible(); + + await viewToggle.click(); + await page.getByRole("option", { name: "Meldungen" }).click(); + + for (const header of [ + "Prio", + "Zeitstempel", + "Quelle", + "Meldung", + "Status", + ]) { + await highlightAndExpectVisible( + page, + dialog.getByRole("cell", { name: header }) + ); + } + + await expect(dialog.getByText("Von")).not.toBeVisible(); + await expect(dialog.getByText("Bis")).not.toBeVisible(); + + await viewToggle.click(); + await page.getByRole("option", { name: "Messkurve" }).click(); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +}); diff --git a/playwright/tests/components/main/kabelueberwachung/isoModalTest.ts b/playwright/tests/components/main/kabelueberwachung/isoModalTest.ts new file mode 100644 index 0000000..82980b5 --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/isoModalTest.ts @@ -0,0 +1,104 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * ISO Modal UI / Behavior Regression Test + * Covers: + * - Opening ISO modal from cable monitoring page + * - Verifies modal structure (header, fullscreen + close buttons) + * - Dropdown (Messkurve <-> Meldungen) presence & options + * - Toolbar elements (KÜ badge, DateRangePicker fields, Mode dropdown, Daten laden button) + * - Style smoke checks via class attributes (non brittle, only key tokens) + * - Switching to Meldungen hides date + mode controls and shows table headers + */ +test.describe("ISO Modal", () => { + test("opens and validates core UI + view toggle behavior", async ({ + page, + }) => { + await page.goto("/kabelueberwachung"); + + // Open first ISO modal (assumes button text like "Isolationswiderstand" appears after click on a cable cell) + // Reuse existing pattern: click cable card primary action button (blue) first occurrence + const firstChartButton = page + .locator(".bg-littwin-blue.text-white") + .first(); + await highlightAndExpectVisible(page, firstChartButton); + await firstChartButton.click(); + + // Heading + const heading = page.getByRole("heading", { name: "Isolationswiderstand" }); + await highlightAndExpectVisible(page, heading); + + // Modal root (dialog) + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + // Header icon buttons (fullscreen + close) by role=button count (2) + await expect( + dialog + .locator("button:has(i.bi-fullscreen-exit, i.bi-arrows-fullscreen)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + // Dropdown for Messkurve / Meldungen (Listbox button): should show current selection (default Messkurve) + const viewToggle = dialog.getByRole("button", { + name: /Messkurve|Meldungen/, + }); + await highlightAndExpectVisible(page, viewToggle); + + // Toolbar smoke checks + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + // Date range inputs (Von / Bis labels present when Messkurve) + await expect(dialog.getByText("Von")).toBeVisible(); + await expect(dialog.getByText("Bis")).toBeVisible(); + + // Mode dropdown (Alle Messwerte / Stündlich / Täglich) – open and verify options + const modeBtn = dialog.getByRole("button", { + name: /Alle Messwerte|Stündlich|Täglich/, + }); + await modeBtn.click(); + await expect( + page.getByRole("option", { name: /Alle Messwerte/ }) + ).toBeVisible(); + await expect(page.getByRole("option", { name: /Stündlich/ })).toBeVisible(); + await expect(page.getByRole("option", { name: /Täglich/ })).toBeVisible(); + // Select Stündlich then reopen to restore Alle Messwerte + await page.getByRole("option", { name: "Stündlich" }).click(); + await modeBtn.click(); + await page.getByRole("option", { name: "Alle Messwerte" }).click(); + + // Daten laden button + const loadBtn = dialog.getByRole("button", { name: "Daten laden" }); + await expect(loadBtn).toBeVisible(); + + // Switch to Meldungen view via view dropdown + await viewToggle.click(); + await page.getByRole("option", { name: "Meldungen" }).click(); + + // In Meldungen: Expect table headers (Prio, Zeitstempel, Quelle, Meldung, Status) + for (const header of [ + "Prio", + "Zeitstempel", + "Quelle", + "Meldung", + "Status", + ]) { + await highlightAndExpectVisible( + page, + dialog.getByRole("cell", { name: header }) + ); + } + + // Date range + mode controls should be hidden (opacity 0 OR not visible). We assert not visible. + await expect(dialog.getByText("Von")).not.toBeVisible(); + await expect(dialog.getByText("Bis")).not.toBeVisible(); + + // Return to Messkurve and verify canvas appears (chart may load async – wait for any canvas) + await viewToggle.click(); + await page.getByRole("option", { name: "Messkurve" }).click(); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +}); diff --git a/playwright/tests/components/main/kabelueberwachung/loopModal.test.ts b/playwright/tests/components/main/kabelueberwachung/loopModal.test.ts new file mode 100644 index 0000000..f7f0ad4 --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/loopModal.test.ts @@ -0,0 +1,72 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * Loop (RSL) Modal UI / Behavior Regression Test + */ +test.describe("Loop / RSL Modal", () => { + test("opens and validates RSL UI & controls", async ({ page }) => { + await page.goto("/kabelueberwachung"); + + const firstBlue = page.locator(".bg-littwin-blue.text-white").first(); + await highlightAndExpectVisible(page, firstBlue); + await firstBlue.click(); + + const heading = page.getByRole("heading", { + name: /Schleifenwiderstand|Isolationswiderstand/, + }); + await heading.waitFor(); + + if ( + await page + .getByRole("heading", { name: "Isolationswiderstand" }) + .isVisible() + .catch(() => false) + ) { + await page.locator("button:has(i.bi-x-lg)").click(); + const secondBlue = page.locator(".bg-littwin-blue.text-white").nth(1); + await highlightAndExpectVisible(page, secondBlue); + await secondBlue.click(); + await highlightAndExpectVisible( + page, + page.getByRole("heading", { name: "Schleifenwiderstand" }) + ); + } + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + await expect( + dialog + .locator("button:has(i.bi-arrows-fullscreen, i.bi-fullscreen-exit)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + await expect(dialog.getByText("Von")).toBeVisible(); + await expect(dialog.getByText("Bis")).toBeVisible(); + + const modeBtn = dialog.getByRole("button", { + name: /Alle Messwerte|Stündlich|Täglich/, + }); + await modeBtn.click(); + await expect( + page.getByRole("option", { name: /Alle Messwerte/ }) + ).toBeVisible(); + await expect(page.getByRole("option", { name: /Stündlich/ })).toBeVisible(); + await expect(page.getByRole("option", { name: /Täglich/ })).toBeVisible(); + await page.getByRole("option", { name: "Stündlich" }).click(); + + const rslButton = dialog.getByRole("button", { name: /RSL/i }); + await expect(rslButton).toBeVisible(); + + const loadBtn = dialog.getByRole("button", { name: "Daten laden" }); + await expect(loadBtn).toBeVisible(); + await loadBtn.click(); + await dialog.locator("canvas").waitFor({ timeout: 5000 }); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +}); diff --git a/playwright/tests/components/main/kabelueberwachung/loopModalTest.ts b/playwright/tests/components/main/kabelueberwachung/loopModalTest.ts new file mode 100644 index 0000000..f3b0a25 --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/loopModalTest.ts @@ -0,0 +1,92 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * Loop (RSL) Modal UI / Behavior Regression Test + * Covers: + * - Opening Schleifenwiderstand modal + * - Fullscreen + close buttons + * - View dropdown (Messkurve / Meldungen) using loop slice title + * - Toolbar elements + RSL Start button + progress overlay simulation (dev only) + * - Mode dropdown copy + */ + +test.describe("Loop / RSL Modal", () => { + test("opens and validates RSL UI & controls", async ({ page }) => { + await page.goto("/kabelueberwachung"); + + // Open first Loop modal (assumes second blue button or identical). Use heading filter first. + // Fallback: click the same first blue button again then switch heading if needed. + const firstBlue = page.locator(".bg-littwin-blue.text-white").first(); + await highlightAndExpectVisible(page, firstBlue); + await firstBlue.click(); + + // If heading not Schleifenwiderstand, close & pick another? We'll accept either heading present. + const heading = page.getByRole("heading", { + name: /Schleifenwiderstand|Isolationswiderstand/, + }); + await heading.waitFor(); + + // If we landed in Iso instead of Loop, try second blue button inside the cable card bar. + if ( + await page + .getByRole("heading", { name: "Isolationswiderstand" }) + .isVisible() + .catch(() => false) + ) { + // Close and reopen to get loop: click close icon then second blue button (if available) + await page.locator("button:has(i.bi-x-lg)").click(); + const secondBlue = page.locator(".bg-littwin-blue.text-white").nth(1); + await highlightAndExpectVisible(page, secondBlue); + await secondBlue.click(); + await highlightAndExpectVisible( + page, + page.getByRole("heading", { name: "Schleifenwiderstand" }) + ); + } + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + // Fullscreen + close icons + await expect( + dialog + .locator("button:has(i.bi-arrows-fullscreen, i.bi-fullscreen-exit)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + // Toolbar presence + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + // Date labels visible in Messkurve + await expect(dialog.getByText("Von")).toBeVisible(); + await expect(dialog.getByText("Bis")).toBeVisible(); + + // Mode dropdown + const modeBtn = dialog.getByRole("button", { + name: /Alle Messwerte|Stündlich|Täglich/, + }); + await modeBtn.click(); + await expect( + page.getByRole("option", { name: /Alle Messwerte/ }) + ).toBeVisible(); + await expect(page.getByRole("option", { name: /Stündlich/ })).toBeVisible(); + await expect(page.getByRole("option", { name: /Täglich/ })).toBeVisible(); + await page.getByRole("option", { name: "Stündlich" }).click(); + + // RSL Start button (text may be dynamic; look for partial) + const rslButton = dialog.getByRole("button", { name: /RSL/i }); + await expect(rslButton).toBeVisible(); + + // Daten laden button present + const loadBtn = dialog.getByRole("button", { name: "Daten laden" }); + await expect(loadBtn).toBeVisible(); + + // Chart canvas should appear (allow some time after load click) + await loadBtn.click(); + await dialog.locator("canvas").waitFor({ timeout: 5000 }); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +}); diff --git a/playwright/tests/components/main/kabelueberwachung/tdrModal.test.ts b/playwright/tests/components/main/kabelueberwachung/tdrModal.test.ts new file mode 100644 index 0000000..8576332 --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/tdrModal.test.ts @@ -0,0 +1,93 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * TDR Modal UI / Behavior Regression Test + */ +test.describe("TDR Modal", () => { + test("opens and validates TDR UI + dropdown + actions", async ({ page }) => { + await page.goto("/kabelueberwachung"); + + const blueButtons = page.locator(".bg-littwin-blue.text-white"); + const count = await blueButtons.count(); + for (let i = 0; i < Math.min(count, 4); i++) { + await highlightAndExpectVisible(page, blueButtons.nth(i)); + await blueButtons.nth(i).click(); + if ( + await page + .getByRole("heading", { name: "TDR-Messung" }) + .isVisible() + .catch(() => false) + ) { + break; + } else { + if ( + await page + .locator("button:has(i.bi-x-lg)") + .isVisible() + .catch(() => false) + ) { + await page.locator("button:has(i.bi-x-lg)").click(); + } + } + } + + const heading = page.getByRole("heading", { name: "TDR-Messung" }); + await heading.waitFor(); + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + await expect( + dialog + .locator("button:has(i.bi-arrows-fullscreen, i.bi-fullscreen-exit)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + const refBtn = dialog.getByRole("button", { + name: "TDR-Kurve als Referenz speichern", + }); + await expect(refBtn).toBeVisible(); + const startBtn = dialog.getByRole("button", { + name: /TDR-Messung starten|TDR läuft/, + }); + await expect(startBtn).toBeVisible(); + + const measurementBtn = dialog.getByRole("button", { name: /Fehlerstelle/ }); + await highlightAndExpectVisible(page, measurementBtn); + await measurementBtn.click(); + + const option = page.getByRole("option", { name: /Fehlerstelle/ }).first(); + await option.waitFor(); + await expect(option).toBeVisible(); + await option.click(); + await expect(measurementBtn).toContainText("Fehlerstelle"); + + const viewToggle = dialog.getByRole("button", { + name: /Messkurve|Meldungen/, + }); + await viewToggle.click(); + await page.getByRole("option", { name: "Meldungen" }).click(); + + for (const header of [ + "Prio", + "Zeitstempel", + "Quelle", + "Meldung", + "Status", + ]) { + await highlightAndExpectVisible( + page, + dialog.getByRole("cell", { name: header }) + ); + } + + await viewToggle.click(); + await page.getByRole("option", { name: "Messkurve" }).click(); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +}); diff --git a/playwright/tests/components/main/kabelueberwachung/tdrModalTest.ts b/playwright/tests/components/main/kabelueberwachung/tdrModalTest.ts new file mode 100644 index 0000000..0f4a80d --- /dev/null +++ b/playwright/tests/components/main/kabelueberwachung/tdrModalTest.ts @@ -0,0 +1,113 @@ +import { test, expect } from "@playwright/test"; +import { highlightAndExpectVisible } from "@playwright/utils/highlight"; + +/** + * TDR Modal UI / Behavior Regression Test + * Covers: + * - Opening TDR-Messung modal + * - Header elements (fullscreen + close) + * - Toolbar buttons: reference save + start TDR measurement + * - Measurement dropdown styling & options list formatting + * - View toggle (Messkurve / Meldungen) path reused from ISO + */ + +test.describe("TDR Modal", () => { + test("opens and validates TDR UI + dropdown + actions", async ({ page }) => { + await page.goto("/kabelueberwachung"); + + // Strategy: open a cable card then open TDR via iterative attempts. + // We look for a heading 'TDR-Messung' after clicking blue buttons. + const blueButtons = page.locator(".bg-littwin-blue.text-white"); + const count = await blueButtons.count(); + for (let i = 0; i < Math.min(count, 4); i++) { + await highlightAndExpectVisible(page, blueButtons.nth(i)); + await blueButtons.nth(i).click(); + if ( + await page + .getByRole("heading", { name: "TDR-Messung" }) + .isVisible() + .catch(() => false) + ) { + break; + } else { + // Close and try next if wrong modal + if ( + await page + .locator("button:has(i.bi-x-lg)") + .isVisible() + .catch(() => false) + ) { + await page.locator("button:has(i.bi-x-lg)").click(); + } + } + } + + const heading = page.getByRole("heading", { name: "TDR-Messung" }); + await heading.waitFor(); + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + + // Header buttons + await expect( + dialog + .locator("button:has(i.bi-arrows-fullscreen, i.bi-fullscreen-exit)") + .first() + ).toBeVisible(); + await expect(dialog.locator("button:has(i.bi-x-lg)")).toBeVisible(); + + // Toolbar base + await expect(dialog.locator(".toolbar")).toBeVisible(); + await expect(dialog.locator(".toolbar").getByText("KÜ")).toBeVisible(); + + // Reference save & start buttons + const refBtn = dialog.getByRole("button", { + name: "TDR-Kurve als Referenz speichern", + }); + await expect(refBtn).toBeVisible(); + const startBtn = dialog.getByRole("button", { + name: /TDR-Messung starten|TDR läuft/, + }); + await expect(startBtn).toBeVisible(); + + // Measurement dropdown button: Should contain formatted pattern "Fehlerstelle:" when a selection exists + const measurementBtn = dialog.getByRole("button", { name: /Fehlerstelle/ }); + await highlightAndExpectVisible(page, measurementBtn); + await measurementBtn.click(); + + // Options list - wait for at least one option containing Fehlerstelle + const option = page.getByRole("option", { name: /Fehlerstelle/ }).first(); + await option.waitFor(); + await expect(option).toBeVisible(); + + // Select first option and verify button text updates (optional assertion already implicit) + await option.click(); + await expect(measurementBtn).toContainText("Fehlerstelle"); + + // Switch to Meldungen view using view dropdown in header + const viewToggle = dialog.getByRole("button", { + name: /Messkurve|Meldungen/, + }); + await viewToggle.click(); + await page.getByRole("option", { name: "Meldungen" }).click(); + + // Expect Meldungen columns + for (const header of [ + "Prio", + "Zeitstempel", + "Quelle", + "Meldung", + "Status", + ]) { + await highlightAndExpectVisible( + page, + dialog.getByRole("cell", { name: header }) + ); + } + + // Switch back to Messkurve + await viewToggle.click(); + await page.getByRole("option", { name: "Messkurve" }).click(); + await expect(dialog.locator("canvas")).toBeVisible(); + }); +});