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>
182 lines
7.1 KiB
TypeScript
182 lines
7.1 KiB
TypeScript
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()
|
||
})
|
||
})
|