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
@@ -1,4 +1,5 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { anmelden } from "./wehr";
|
||||
|
||||
/**
|
||||
* E2E-Tests für Phase 1.
|
||||
@@ -10,17 +11,6 @@ import { expect, test, type Page } from "@playwright/test";
|
||||
* Voraussetzung: Stack läuft, Datenbank ist migriert und geseedet.
|
||||
*/
|
||||
|
||||
const ADMIN_EMAIL = process.env.SEED_ADMIN_EMAIL ?? "admin@beispielwehr.de";
|
||||
const ADMIN_PASSWORT = process.env.SEED_ADMIN_PASSWORT ?? "Feuerwehr2026";
|
||||
|
||||
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.beforeEach(async ({ page }) => {
|
||||
await anmelden(page);
|
||||
});
|
||||
@@ -42,11 +32,17 @@ test.describe("Geräteliste", () => {
|
||||
await page.getByLabel("Geräte durchsuchen").fill("Pressluftatmer");
|
||||
|
||||
// Die Suche ist entprellt — deshalb auf das Ergebnis warten, nicht auf die Zeit.
|
||||
await expect(page.getByRole("cell", { name: "Pressluftatmer PA 94" }).first()).toBeVisible();
|
||||
await expect(page.getByRole("cell", { name: "Feuerwehrhelm F220" })).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByRole("cell", { name: "Pressluftatmer PA 94" }).first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("cell", { name: "Feuerwehrhelm F220" }),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("meldet verständlich, wenn die Suche nichts findet", async ({ page }) => {
|
||||
test("meldet verständlich, wenn die Suche nichts findet", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/geraete");
|
||||
await page.getByLabel("Geräte durchsuchen").fill("gibtesnichtxyz");
|
||||
|
||||
@@ -55,22 +51,30 @@ test.describe("Geräteliste", () => {
|
||||
|
||||
test("blendet Spalten aus und wieder ein", async ({ page }) => {
|
||||
await page.goto("/geraete");
|
||||
await expect(page.getByRole("columnheader", { name: /Hersteller/i })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /Hersteller/i }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Spalten" }).click();
|
||||
await page.getByRole("checkbox", { name: "Hersteller" }).click();
|
||||
|
||||
await expect(page.getByRole("columnheader", { name: /Hersteller/i })).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /Hersteller/i }),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("zeigt Sammelaktionen, sobald etwas ausgewählt ist", async ({ page }) => {
|
||||
test("zeigt Sammelaktionen, sobald etwas ausgewählt ist", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/geraete");
|
||||
await expect(page.getByRole("link", { name: "INV-0001" })).toBeVisible();
|
||||
|
||||
await page.getByRole("checkbox", { name: "INV-0001 auswählen" }).click();
|
||||
|
||||
await expect(page.getByText("1 Gerät ausgewählt")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Etiketten drucken" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Etiketten drucken" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,14 +100,18 @@ test.describe("Gerätedetail", () => {
|
||||
});
|
||||
|
||||
test.describe("QR-Route", () => {
|
||||
test("leitet von /g/<inventarnummer> auf das Gerät weiter", async ({ page }) => {
|
||||
test("leitet von /g/<inventarnummer> auf das Gerät weiter", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/g/INV-0001");
|
||||
|
||||
await expect(page).toHaveURL(/\/geraete\/[0-9a-f-]{36}/);
|
||||
await expect(page.getByText("INV-0001").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("erklärt verständlich, wenn es die Nummer nicht gibt", async ({ page }) => {
|
||||
test("erklärt verständlich, wenn es die Nummer nicht gibt", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/g/INV-GIBTESNICHT");
|
||||
|
||||
await expect(page.getByText("Kein Gerät gefunden")).toBeVisible();
|
||||
@@ -126,13 +134,19 @@ test.describe("Fahrzeuge", () => {
|
||||
test("zeigt die Fahrzeuge als Kacheln", async ({ page }) => {
|
||||
await page.goto("/fahrzeuge");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Fahrzeuge" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: /Florian Musterhausen 40\/1/ })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Fahrzeuge" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("link", { name: /Florian Musterhausen 40\/1/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("zeigt die Beladung nach Geräteräumen", async ({ page }) => {
|
||||
await page.goto("/fahrzeuge");
|
||||
await page.getByRole("link", { name: /Florian Musterhausen 40\/1/ }).click();
|
||||
await page
|
||||
.getByRole("link", { name: /Florian Musterhausen 40\/1/ })
|
||||
.click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Beladung" })).toBeVisible();
|
||||
// Die Beladeordnung aus dem Seed: G1 bis G6, Heck, Dach, Mannschaftsraum.
|
||||
@@ -145,10 +159,14 @@ test.describe("Qualifikationen", () => {
|
||||
test("zeigt Ablaufwarnungen und den Katalog", async ({ page }) => {
|
||||
await page.goto("/qualifikationen");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Qualifikationen" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Qualifikationen" }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Läuft ab" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Katalog" })).toBeVisible();
|
||||
await expect(page.getByText("Atemschutzgeräteträger").first()).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("Atemschutzgeräteträger").first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -156,25 +174,39 @@ test.describe("Mitglieder", () => {
|
||||
test("gruppiert nach Abteilung", async ({ page }) => {
|
||||
await page.goto("/mitglieder");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Mitglieder" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: /^Aktive \(/ })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Huber, Andreas" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Mitglieder" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: /^Aktive \(/ }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("link", { name: "Huber, Andreas" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("zeigt Qualifikationen am Mitglied", async ({ page }) => {
|
||||
await page.goto("/mitglieder");
|
||||
await page.getByRole("link", { name: "Huber, Andreas" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Qualifikationen" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Terminal-Zugang" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Qualifikationen" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Terminal-Zugang" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Import", () => {
|
||||
test("führt durch die drei Schritte und zeigt die Vorschau", async ({ page }) => {
|
||||
test("führt durch die drei Schritte und zeigt die Vorschau", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/geraete/importieren");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "1. Datei wählen" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "1. Datei wählen" }),
|
||||
).toBeVisible();
|
||||
|
||||
const csv = [
|
||||
"Inventarnummer;Bezeichnung;Hersteller;Indienststellung",
|
||||
@@ -189,15 +221,23 @@ test.describe("Import", () => {
|
||||
});
|
||||
|
||||
// Schritt 2 erscheint erst, wenn der Server die Datei gelesen hat.
|
||||
await expect(page.getByRole("heading", { name: "2. Spalten zuordnen" })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "2. Spalten zuordnen" }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Vorschau" })).toBeVisible();
|
||||
|
||||
// Eine Zeile ist gut, eine hat ein unlesbares Datum.
|
||||
await expect(page.getByText("1 von 2 Zeilen sind übernehmbar")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /1 Zeilen übernehmen/ })).toBeEnabled();
|
||||
await expect(
|
||||
page.getByText("1 von 2 Zeilen sind übernehmbar"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: /1 Zeilen übernehmen/ }),
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("verweigert die Übernahme, wenn ein Pflichtfeld nicht zugeordnet ist", async ({ page }) => {
|
||||
test("verweigert die Übernahme, wenn ein Pflichtfeld nicht zugeordnet ist", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/geraete/importieren");
|
||||
|
||||
await page.setInputFiles("#datei", {
|
||||
@@ -206,6 +246,8 @@ test.describe("Import", () => {
|
||||
buffer: Buffer.from("Inventarnummer\nE2E-100\n", "utf8"),
|
||||
});
|
||||
|
||||
await expect(page.getByText(/Diesen Pflichtfeldern ist keine Spalte zugeordnet/)).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(/Diesen Pflichtfeldern ist keine Spalte zugeordnet/),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user