import { expect, test, type Page } from "@playwright/test"; import "dotenv/config"; import { prisma } from "@/lib/db"; /** * Das Archiv am gebauten Stand (#015). * * Die Suche selbst prueft `tests/archiv.test.ts` gegen die Datenbank, bis in die * Mandantengrenze hinein. Hier geht es um den Weg, den ein Mensch nimmt: Wort * eintippen, Treffer sehen, **erkennen warum**, in den Lauf springen. Ein * Treffer ohne sichtbaren Grund ist fuer den Suchenden wertlos. */ const STEMPEL = Date.now(); const NOTIZ = `Restposten ${STEMPEL} aus dem Lager`; async function anmelden(seite: Page) { const email = process.env["SEED_ADMIN_EMAIL"]; const passwort = process.env["SEED_ADMIN_PASSWORD"]; if (!email || !passwort) throw new Error("SEED_ADMIN_EMAIL und SEED_ADMIN_PASSWORD fehlen."); await seite.goto("/anmelden"); await seite.fill('input[name="email"]', email); await seite.fill('input[name="passwort"]', passwort); await Promise.all([seite.waitForURL("**/"), seite.click('button[type="submit"]')]); } /** * Ein archivierter Lauf mit einer Notiz, die es sonst nirgends gibt. * * Direkt ueber Prisma und nicht ueber die Dienste: hier soll **das Archiv** * geprueft werden, nicht der Weg dorthin — den prueft `tests/e2e/laeufe.spec.ts` * vom Anlegen bis zum Archivieren. Die Zeitstempel gehoeren trotzdem dazu, drei * CHECK-Bedingungen in der Datenbank verlangen sie. */ async function archivierterLauf(): Promise<{ id: string; titel: string }> { const version = await prisma.vorlagenVersion.findFirstOrThrow({ where: { status: "VEROEFFENTLICHT" }, orderBy: { veroeffentlichtAm: "desc" }, select: { id: true, abschnitte: { select: { schritte: { select: { id: true } } } } }, }); const kunde = await prisma.kunde.findFirstOrThrow({ select: { id: true } }); const schrittId = version.abschnitte.flatMap((abschnitt) => abschnitt.schritte)[0]?.id; if (!schrittId) throw new Error("Die Vorlage hat keinen Schritt."); const jetzt = new Date(); const titel = `ZZ E2E Archiv ${STEMPEL}`; const lauf = await prisma.lauf.create({ data: { vorlagenVersionId: version.id, kundeId: kunde.id, titel, status: "ARCHIVIERT", gestartetAm: jetzt, abgeschlossenAm: jetzt, archiviertAm: jetzt, }, }); await prisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schrittId, zustand: "NICHT_ZUTREFFEND", notiz: NOTIZ, }, }); return { id: lauf.id, titel }; } test.describe("Archiv", () => { test("findet einen Lauf über ein Wort aus einer Schritt-Notiz", async ({ page }) => { const lauf = await archivierterLauf(); await anmelden(page); // Am Handy steckt die Navigation im Menü – der Weg dorthin gehört zum Test. if (test.info().project.name === "handy") { await page.getByLabel("Menü").click(); } await page.getByRole("link", { name: "Archiv" }).first().click(); await page.waitForURL("**/archiv"); await page.getByLabel("Suchbegriff").fill(`Restposten ${STEMPEL}`); await page.getByRole("button", { name: "Suchen" }).click(); const treffer = page.getByRole("listitem").filter({ hasText: lauf.titel }); await expect(treffer).toHaveCount(1); // Und der Grund steht dabei: Fundstelle und Ausschnitt. await expect(treffer.getByText("Notiz")).toBeVisible(); await expect(treffer.getByText(/Restposten/)).toBeVisible(); await treffer.getByRole("link").first().click(); await page.waitForURL(`**/laeufe/${lauf.id}`); await expect(page.getByText(/Dieser Lauf ist archiviert/)).toBeVisible(); }); test("sagt, dass ein einzelnes Zeichen nicht sucht", async ({ page }) => { // Ein stilles Nichtstun wäre die schlechtere Antwort: der Suchende wartet. await anmelden(page); await page.goto("/archiv?suche=a"); // Die Meldung **zum Ergebnis**, nicht der Hinweis am Formular: beide nennen // die zwei Zeichen, und nur die erste sagt, dass gerade nichts gesucht wurde. await expect(page.getByText(/ist als Suchbegriff zu kurz/)).toBeVisible(); }); test("trägt am Handy ohne horizontales Scrollen", async ({ page, isMobile }) => { test.skip(!isMobile, "Nur im Projekt „handy“."); await anmelden(page); await page.goto("/archiv?suche=Restposten"); const masse = await page.evaluate(() => ({ scroll: document.documentElement.scrollWidth, sichtbar: document.documentElement.clientWidth, })); expect(masse.scroll).toBeLessThanOrEqual(masse.sichtbar); }); }); test.afterAll(async () => { const laeufe = await prisma.lauf.findMany({ where: { titel: { startsWith: "ZZ E2E Archiv" } }, select: { id: true }, }); const kennungen = laeufe.map((lauf) => lauf.id); if (kennungen.length > 0) { await prisma.schrittergebnis.deleteMany({ where: { laufId: { in: kennungen } } }); await prisma.protokoll.deleteMany({ where: { OR: [{ laufId: { in: kennungen } }, { entitaetId: { in: kennungen } }] }, }); await prisma.lauf.deleteMany({ where: { id: { in: kennungen } } }); } await prisma.$disconnect(); });