Files
planer/e2e/aufgaben.spec.ts
T
Florian WildeandClaude Opus 5 7919149879 fix: Navigation aufteilen, statt sie weiter zu dehnen
Mit dem dreizehnten Eintrag verschluckte die Leiste erneut Punkte am
rechten Rand — nach M6 zum zweiten Mal. Das Problem ist nicht die Breite,
sondern die Vermischung: Einrichtung und Verwaltung sucht man nicht beim
Arbeiten. Alltag bleibt in der Leiste, alles andere liegt hinter "Mehr".

Ein <details> statt eines Menues aus Skript: Es klappt ohne JavaScript
auf und braucht keine Zustandsverwaltung.

Zurueckgezogene Tokens stehen jetzt unten. Was noch Zugang hat, ist das,
wonach man sucht.

Zwei E2E-Tests greifen den neuen Weg ueber "Mehr" auf. Ein dritter wartet
das Neuladen ab, bevor er navigiert — die Meldung kommt frueher als die
fertige Liste, und Chromium bricht die Navigation sonst ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:52:35 +02:00

182 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
})
})