import { PrismaPg } from '@prisma/adapter-pg' import { expect, test } from '@playwright/test' import { PrismaClient } from '../src/generated/prisma/client.js' import { anmelden, EMAIL, PASSWORT, VERBINDUNG } from './hilfe' /** * Der Weg, der in M2 wirklich zählt: erfassen, einsortieren, abhaken. * * Alle Testdaten tragen ein gemeinsames Präfix und werden am Ende entfernt, * damit die Entwicklungsdatenbank hinterher aussieht wie vorher. */ const PRAEFIX = `E2E-${Date.now()}` const db = new PrismaClient({ adapter: new PrismaPg({ connectionString: VERBINDUNG }) }) test.describe('Aufgaben', () => { test.skip( !EMAIL || !PASSWORT || !VERBINDUNG, 'Ohne ADMIN_EMAIL, ADMIN_PASSWORT und DATABASE_URL gibt es kein Testkonto.', ) test.afterAll(async () => { await db.aufgabe.deleteMany({ where: { titel: { startsWith: PRAEFIX } } }) await db.projekt.deleteMany({ where: { name: { startsWith: PRAEFIX } } }) await db.auditEintrag.deleteMany({ where: { aktion: { startsWith: 'aufgabe.' } } }) await db.$disconnect() }) test('erfasst schnell, sortiert ein und hakt ab', async ({ page }) => { const titel = `${PRAEFIX} Angebot schreiben` await anmelden(page) // --- Erfassen: ein Feld genügt -------------------------------------- await page.goto('/inbox') await page.getByLabel('Neue Aufgabe').fill(titel) await page.getByRole('button', { name: 'Erfassen' }).click() const zeile = page.locator('li').filter({ hasText: titel }) await expect(zeile).toBeVisible() // Ohne Bereich liegt sie in der Inbox – genau das ist der Unterschied. await expect(zeile.getByText('Inbox')).toBeVisible() // --- Einsortieren ---------------------------------------------------- const bereich = await db.bereich.findFirst({ where: { aktiv: true }, orderBy: { reihenfolge: 'asc' }, select: { id: true, name: true }, }) expect(bereich).not.toBeNull() await zeile.getByLabel('Einsortieren nach').selectOption(bereich!.id) await expect(page.getByText(`liegt jetzt in ${bereich!.name}`)).toBeVisible() // Die Meldung kommt, bevor das Neuladen der Liste fertig ist. Wer jetzt // navigiert, bricht es ab — und Chromium meldet ERR_ABORTED. await page.waitForLoadState('networkidle') // Aus der Inbox ist sie damit verschwunden. await page.goto('/inbox') await expect(page.locator('li').filter({ hasText: titel })).toHaveCount(0) // --- Abhaken --------------------------------------------------------- await page.goto('/aufgaben') const inListe = page.locator('li').filter({ hasText: titel }) await expect(inListe).toBeVisible() await inListe.getByRole('button', { name: 'Als erledigt abhaken' }).click() // Auf die Bestätigung warten, bevor es weitergeht: der Klick stößt eine // Server Action an, und ohne diesen Haltepunkt navigiert der Test // gelegentlich schneller, als gespeichert wird. await expect(page.getByText('ist jetzt erledigt')).toBeVisible() // Erledigtes fällt aus der Standardansicht heraus. await page.goto('/aufgaben') await expect(page.locator('li').filter({ hasText: titel })).toHaveCount(0) // Mit Filter "Alle" ist sie wieder da. await page.goto('/aufgaben?status=ERLEDIGT') await expect(page.locator('li').filter({ hasText: titel })).toBeVisible() const gespeichert = await db.aufgabe.findFirst({ where: { titel } }) expect(gespeichert?.status).toBe('ERLEDIGT') expect(gespeichert?.erledigtAm).not.toBeNull() }) test('macht aus einer Idee eine Aufgabe', async ({ page }) => { const titel = `${PRAEFIX} Blog aufsetzen` await anmelden(page) await page.goto('/ideen') await page.getByLabel('Neue Idee').fill(titel) await page.getByRole('button', { name: 'Erfassen' }).click() const zeile = page.locator('li').filter({ hasText: titel }) await expect(zeile).toBeVisible() await zeile.getByRole('button', { name: 'Zur Aufgabe machen' }).click() await expect(page.getByText('ist jetzt eine Aufgabe')).toBeVisible() const danach = await db.aufgabe.findFirst({ where: { titel } }) expect(danach?.typ).toBe('AUFGABE') // Aus der Ideenliste ist sie damit verschwunden. await page.goto('/ideen') await expect(page.locator('li').filter({ hasText: titel })).toHaveCount(0) }) test('hält Ideen aus der Aufgabenliste heraus', async ({ page }) => { const idee = `${PRAEFIX} Nur eine Idee` const aufgabe = `${PRAEFIX} Eine echte Aufgabe` await db.aufgabe.createMany({ data: [ { titel: idee, typ: 'IDEE' }, { titel: aufgabe, typ: 'AUFGABE' }, ], }) await anmelden(page) // In der Aufgabenliste hat die Idee nichts zu suchen. await page.goto('/aufgaben') await expect(page.locator('li').filter({ hasText: aufgabe })).toBeVisible() await expect(page.locator('li').filter({ hasText: idee })).toHaveCount(0) // Auch nicht in der Inbox, obwohl beide keinen Bereich haben. await page.goto('/inbox') await expect(page.locator('li').filter({ hasText: aufgabe })).toBeVisible() await expect(page.locator('li').filter({ hasText: idee })).toHaveCount(0) // Wer ausdrücklich danach filtert, sieht sie sehr wohl. await page.goto('/aufgaben?typ=IDEE') await expect(page.locator('li').filter({ hasText: idee })).toBeVisible() await expect(page.locator('li').filter({ hasText: aufgabe })).toHaveCount(0) }) test('filtert nach Bereich und über die Adresszeile', async ({ page }) => { await anmelden(page) const bereich = await db.bereich.findFirst({ where: { aktiv: true }, select: { id: true, name: true }, }) const drin = `${PRAEFIX} Gehört dazu` const draussen = `${PRAEFIX} Gehört nicht dazu` await db.aufgabe.createMany({ data: [ { titel: drin, bereichId: bereich!.id }, { titel: draussen, bereichId: null }, ], }) await page.goto(`/aufgaben?bereich=${bereich!.id}`) await expect(page.locator('li').filter({ hasText: drin })).toBeVisible() await expect(page.locator('li').filter({ hasText: draussen })).toHaveCount(0) // Nur Inbox zeigt genau die andere. await page.goto('/aufgaben?bereich=inbox') await expect(page.locator('li').filter({ hasText: draussen })).toBeVisible() await expect(page.locator('li').filter({ hasText: drin })).toHaveCount(0) }) test('zeigt den Fortschritt eines Projekts', async ({ page }) => { const projektName = `${PRAEFIX} Website neu` const projekt = await db.projekt.create({ data: { name: projektName } }) await db.aufgabe.createMany({ data: [ { titel: `${PRAEFIX} Entwurf`, projektId: projekt.id, status: 'ERLEDIGT' }, { titel: `${PRAEFIX} Umsetzung`, projektId: projekt.id, status: 'OFFEN' }, // Verworfenes zählt bewusst gar nicht mit. { titel: `${PRAEFIX} Verworfen`, projektId: projekt.id, status: 'VERWORFEN' }, ], }) await anmelden(page) await page.goto(`/projekte/${projekt.id}`) await expect(page.getByRole('heading', { name: projektName })).toBeVisible() await expect(page.getByText('1 von 2 erledigt (50 %)')).toBeVisible() }) })