Aufraeumarbeiten: ESLint, DOM-Verschachtelung, eigene Testwehr
ESLint hatte keine Konfiguration — das Skript "npm run lint" pruefte nichts. Jetzt Flat Config ohne Formatierungsregeln, dafuer mit den Regeln, die die Fehler dieses Projekts gemeldet haetten. Er hat gleich zwei gefunden, die niemand gesucht hatte. Das Badge rendert ein span statt eines div. Damit ist die Meldung "div cannot be a descendant of p" weg, die seit Phase 3 auf der Einsatzseite stand — sie ist der klassische Ausloeser fuer einen Hydration-Fehler. Die E2E-Tests laufen gegen eine eigene Wehr. Nach fuenf Phasen standen in der Beispielwehr 120 Atemschutz-Ueberwachungen statt einer und 115 Geraete statt 45; bei jedem Lauf aenderten sich alle Bildschirmfotos. Der Bestand ist einmal zurueckgesetzt, npm run db:test-reset baut die Testwehr neu auf. Behoben nebenbei: - Die Fehlerflaeche der Karte konnte nie etwas anzeigen. - Zwei useMemo der Lagekarte rechneten bei jedem Rendern neu. - tests/e2e war vom Typecheck ausgenommen und liess einen doppelt deklarierten Bezeichner durch. Closes #10 Closes #11 Closes #12 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8081131eb7
commit
7196c7110c
+79
-35
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { expect, test, type Locator } from "@playwright/test";
|
||||
import { anmelden } from "./wehr";
|
||||
|
||||
/**
|
||||
* E2E-Tests für Phase 6 — die Kartenmodule.
|
||||
@@ -16,9 +17,6 @@ import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
*/
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
const ADMIN_EMAIL = process.env.SEED_ADMIN_EMAIL ?? "admin@beispielwehr.de";
|
||||
const ADMIN_PASSWORT = process.env.SEED_ADMIN_PASSWORT ?? "Feuerwehr2026";
|
||||
|
||||
/**
|
||||
* Die Fläche der Karte, bereit zum Anklicken.
|
||||
*
|
||||
@@ -28,7 +26,9 @@ const ADMIN_PASSWORT = process.env.SEED_ADMIN_PASSWORT ?? "Feuerwehr2026";
|
||||
* gelegentlich im Nichts — und der Test prüft dann eine Strecke aus einem
|
||||
* einzigen Punkt.
|
||||
*/
|
||||
async function kartenflaeche(karte: Locator): Promise<{ x: number; y: number; width: number; height: number }> {
|
||||
async function kartenflaeche(
|
||||
karte: Locator,
|
||||
): Promise<{ x: number; y: number; width: number; height: number }> {
|
||||
await karte.scrollIntoViewIfNeeded();
|
||||
await expect(karte.locator("canvas")).toBeVisible();
|
||||
|
||||
@@ -37,14 +37,6 @@ async function kartenflaeche(karte: Locator): Promise<{ x: number; y: number; wi
|
||||
return flaeche;
|
||||
}
|
||||
|
||||
async function anmelden(page: Page): Promise<void> {
|
||||
await page.goto("/anmelden");
|
||||
await page.getByLabel("E-Mail-Adresse").fill(ADMIN_EMAIL);
|
||||
await page.getByLabel("Passwort").fill(ADMIN_PASSWORT);
|
||||
await page.getByRole("button", { name: "Anmelden" }).click();
|
||||
await page.waitForURL("/");
|
||||
}
|
||||
|
||||
test.describe("Karte", () => {
|
||||
test("zeigt Löschwasser und Objekte aus dem Bestand", async ({ page }) => {
|
||||
await anmelden(page);
|
||||
@@ -69,16 +61,22 @@ test.describe("Karte", () => {
|
||||
// ungeprüft.
|
||||
.toBeGreaterThanOrEqual(20);
|
||||
|
||||
await expect(page.getByRole("button", { name: /Objekte \(\d+\)/ })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: /Objekte \(\d+\)/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("sagt es, wenn kein Kartenhintergrund eingerichtet ist", async ({ page }) => {
|
||||
test("sagt es, wenn kein Kartenhintergrund eingerichtet ist", async ({
|
||||
page,
|
||||
}) => {
|
||||
await anmelden(page);
|
||||
await page.goto("/karte");
|
||||
|
||||
// Im Testlauf gibt es keinen Kartenserver. Die Anwendung muss das sagen
|
||||
// und trotzdem arbeiten — nicht eine leere Fläche zeigen und schweigen.
|
||||
await expect(page.getByTestId("karte-ohne-kacheln")).toContainText("Punkte liegen richtig");
|
||||
await expect(page.getByTestId("karte-ohne-kacheln")).toContainText(
|
||||
"Punkte liegen richtig",
|
||||
);
|
||||
});
|
||||
|
||||
test("findet über die Suche einen bestimmten Hydranten", async ({ page }) => {
|
||||
@@ -87,7 +85,9 @@ test.describe("Karte", () => {
|
||||
|
||||
// „exact": daneben steht die Zelle mit dem Entfernen-Knopf, die den Namen
|
||||
// in ihrer Beschriftung trägt.
|
||||
await expect(page.getByRole("cell", { name: "H-0004", exact: true })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("cell", { name: "H-0004", exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("Weiher Kiesgrube")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -97,8 +97,12 @@ test.describe("Karte", () => {
|
||||
|
||||
// „defekt" und „ungeprüft" müssen lesbar dastehen — eine rote Markierung
|
||||
// allein sagt am Fahrzeug niemandem, was los ist.
|
||||
await expect(page.getByRole("cell", { name: "defekt" }).first()).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: "ungeprüft" }).first()).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("cell", { name: "defekt" }).first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("cell", { name: "ungeprüft" }).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +119,9 @@ test.describe("Objekte und Feuerwehrpläne", () => {
|
||||
|
||||
// Was auf der Anfahrt zählt, steht oben.
|
||||
await expect(page.getByTestId("objekt-detail")).toContainText("Zufahrt");
|
||||
await expect(page.getByTestId("objekt-detail")).toContainText("Sauerstoffflaschen");
|
||||
await expect(page.getByTestId("objekt-detail")).toContainText(
|
||||
"Sauerstoffflaschen",
|
||||
);
|
||||
await expect(page.getByTestId("objekt-detail")).toContainText("FSD");
|
||||
});
|
||||
|
||||
@@ -143,25 +149,37 @@ test.describe("Objekte und Feuerwehrpläne", () => {
|
||||
});
|
||||
|
||||
test.describe("Wasserförderung", () => {
|
||||
test("rechnet eine Strecke durch, sobald zwei Punkte stehen", async ({ page }) => {
|
||||
test("rechnet eine Strecke durch, sobald zwei Punkte stehen", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(120_000);
|
||||
await anmelden(page);
|
||||
await page.goto("/karte/wasserfoerderung");
|
||||
|
||||
await expect(page.getByTestId("foerderplan")).toContainText("Noch keine Strecke");
|
||||
await expect(page.getByTestId("foerderplan")).toContainText(
|
||||
"Noch keine Strecke",
|
||||
);
|
||||
|
||||
// Zwei Punkte in die Karte tippen — die Karte hat keine Kacheln, aber
|
||||
// Koordinaten hat sie trotzdem.
|
||||
const flaeche = await kartenflaeche(page.getByTestId("karte"));
|
||||
|
||||
await page.mouse.click(flaeche.x + flaeche.width * 0.35, flaeche.y + flaeche.height * 0.5);
|
||||
await page.mouse.click(
|
||||
flaeche.x + flaeche.width * 0.35,
|
||||
flaeche.y + flaeche.height * 0.5,
|
||||
);
|
||||
await expect(page.getByRole("heading", { name: "Punkte" })).toBeVisible();
|
||||
|
||||
await page.mouse.click(flaeche.x + flaeche.width * 0.65, flaeche.y + flaeche.height * 0.5);
|
||||
await page.mouse.click(
|
||||
flaeche.x + flaeche.width * 0.65,
|
||||
flaeche.y + flaeche.height * 0.5,
|
||||
);
|
||||
|
||||
// Ab jetzt steht eine Rechnung da: Pumpen, Längen, Höhenunterschied.
|
||||
await expect(page.getByTestId("pumpenzahl")).toBeVisible();
|
||||
await expect(page.getByTestId("foerderplan")).toContainText("Schlauchlängen");
|
||||
await expect(page.getByTestId("foerderplan")).toContainText(
|
||||
"Schlauchlängen",
|
||||
);
|
||||
|
||||
// Und der Satz, der immer dazugehört.
|
||||
await expect(page.getByText(/Planungshilfe/)).toBeVisible();
|
||||
@@ -178,13 +196,21 @@ test.describe("Wasserförderung", () => {
|
||||
// Zwischen den Klicks warten, bis der erste Punkt angekommen ist — sonst
|
||||
// prüft der Test bei einem verschluckten Klick eine Strecke aus einem
|
||||
// einzigen Punkt.
|
||||
await page.mouse.click(flaeche.x + flaeche.width * 0.1, flaeche.y + flaeche.height * 0.5);
|
||||
await page.mouse.click(
|
||||
flaeche.x + flaeche.width * 0.1,
|
||||
flaeche.y + flaeche.height * 0.5,
|
||||
);
|
||||
await expect(page.getByRole("heading", { name: "Punkte" })).toBeVisible();
|
||||
|
||||
await page.mouse.click(flaeche.x + flaeche.width * 0.9, flaeche.y + flaeche.height * 0.5);
|
||||
await page.mouse.click(
|
||||
flaeche.x + flaeche.width * 0.9,
|
||||
flaeche.y + flaeche.height * 0.5,
|
||||
);
|
||||
|
||||
await expect(page.getByText("Abschnitte")).toBeVisible();
|
||||
await expect(page.getByRole("columnheader", { name: "Längen" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: "Längen" }),
|
||||
).toBeVisible();
|
||||
|
||||
const pumpen = Number(await page.getByTestId("pumpenzahl").innerText());
|
||||
expect(pumpen).toBeGreaterThanOrEqual(1);
|
||||
@@ -192,13 +218,18 @@ test.describe("Wasserförderung", () => {
|
||||
});
|
||||
|
||||
test.describe("Lagekarte", () => {
|
||||
test("trägt ein taktisches Zeichen ein und nimmt es zurück", async ({ page }) => {
|
||||
test("trägt ein taktisches Zeichen ein und nimmt es zurück", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.setTimeout(180_000);
|
||||
await anmelden(page);
|
||||
|
||||
// Irgendein Einsatz aus dem Seed.
|
||||
await page.goto("/einsaetze");
|
||||
await page.getByRole("link", { name: /\d{4}-\d{4}/ }).first().click();
|
||||
await page
|
||||
.getByRole("link", { name: /\d{4}-\d{4}/ })
|
||||
.first()
|
||||
.click();
|
||||
await page.waitForURL(/\/einsaetze\/[0-9a-f-]{36}$/);
|
||||
|
||||
const lagekarte = page.getByTestId("lagekarte");
|
||||
@@ -209,13 +240,23 @@ test.describe("Lagekarte", () => {
|
||||
|
||||
const flaeche = await kartenflaeche(lagekarte.getByTestId("karte"));
|
||||
|
||||
await page.mouse.click(flaeche.x + flaeche.width / 2, flaeche.y + flaeche.height / 2);
|
||||
await page.mouse.click(
|
||||
flaeche.x + flaeche.width / 2,
|
||||
flaeche.y + flaeche.height / 2,
|
||||
);
|
||||
|
||||
await expect(lagekarte.getByText("loeschfahrzeug").first()).toBeVisible({ timeout: 30_000 });
|
||||
await expect(lagekarte.getByText("loeschfahrzeug").first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
// Zurücknehmen: es verschwindet von der Karte.
|
||||
await lagekarte.getByRole("button", { name: "zurücknehmen" }).first().click();
|
||||
await expect(lagekarte.getByText("loeschfahrzeug")).toHaveCount(0, { timeout: 30_000 });
|
||||
await lagekarte
|
||||
.getByRole("button", { name: "zurücknehmen" })
|
||||
.first()
|
||||
.click();
|
||||
await expect(lagekarte.getByText("loeschfahrzeug")).toHaveCount(0, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
});
|
||||
|
||||
test("zeigt den Zustand des Abgleichs", async ({ page }) => {
|
||||
@@ -223,7 +264,10 @@ test.describe("Lagekarte", () => {
|
||||
await anmelden(page);
|
||||
|
||||
await page.goto("/einsaetze");
|
||||
await page.getByRole("link", { name: /\d{4}-\d{4}/ }).first().click();
|
||||
await page
|
||||
.getByRole("link", { name: /\d{4}-\d{4}/ })
|
||||
.first()
|
||||
.click();
|
||||
await page.waitForURL(/\/einsaetze\/[0-9a-f-]{36}$/);
|
||||
|
||||
// Wer nicht sieht, dass der Draht weg ist, hält eine alte Lage für die
|
||||
|
||||
Reference in New Issue
Block a user