139 lines
7.1 KiB
TypeScript
139 lines
7.1 KiB
TypeScript
/** QA-1: Becken + Kontakte (FEAT-2) — CRUD, Belegung, 409-Konfliktpfad. */
|
||
import { acceptNextDialog, de, expect, gotoSection, skipUnlessMock, test, uniqueName } from './fixtures'
|
||
|
||
const tb = de.pages.becken
|
||
const tk = de.pages.kontakte
|
||
|
||
test.describe('Becken', () => {
|
||
test('Belegung: Detail zeigt Bewohner mit Link zur Tierakte', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/gehege')
|
||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
||
await expect(page.getByText(`2 ${tb.occupancy.countMany}`)).toBeVisible()
|
||
await expect(page.getByText(tb.occupancy.title)).toBeVisible()
|
||
await page.getByRole('link', { name: /Krümel/ }).click()
|
||
await expect(page.getByText(de.pages.gerbils.detail.masterData)).toBeVisible()
|
||
})
|
||
|
||
test('Löschen eines belegten Beckens meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/gehege')
|
||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||
acceptNextDialog(page)
|
||
await page.getByRole('button', { name: tb.delete.action }).click()
|
||
await expect(page.getByText(tb.delete.conflict)).toBeVisible()
|
||
})
|
||
|
||
test('Reinigungszyklus: überfälliges Becken zeigt Hinweis + Als gereinigt markieren', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/gehege')
|
||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
||
// Maße + Kapazität werden angezeigt
|
||
await expect(page.getByText('120×50 cm')).toBeVisible()
|
||
// Reinigung ist überfällig (nextCleaningDate in der Vergangenheit) -> Warn-Hinweis
|
||
await expect(page.getByText(/Reinigung fällig/)).toBeVisible()
|
||
// Als gereinigt markieren -> letzte Reinigung = heute, Hinweis verschwindet
|
||
await page.getByRole('button', { name: tb.cleaning.markCleaned }).click()
|
||
await expect(page.getByText(/Reinigung fällig/)).toBeHidden()
|
||
})
|
||
|
||
test('Reinigungszyklus: Becken mit Zyklus anlegen zeigt nächste Reinigung', async ({ page }) => {
|
||
await gotoSection(page, de.nav.enclosures)
|
||
await page.getByRole('link', { name: tb.newButton }).click()
|
||
const name = uniqueName('Zyklusbecken')
|
||
await page.getByLabel(`${tb.fields.name} *`).fill(name)
|
||
await page.getByLabel(tb.fields.size).fill('60×40 cm')
|
||
await page.getByLabel(tb.fields.capacity).fill('4')
|
||
await page.getByLabel(tb.fields.lastCleanedDate).fill('2026-06-01')
|
||
await page.getByLabel(tb.fields.cleaningCycleDays).fill('14')
|
||
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||
// nächste Reinigung = 2026-06-01 + 14 Tage = 15.06.2026 (in der def-list-Zeile)
|
||
await expect(page.getByText('15.06.2026', { exact: true })).toBeVisible()
|
||
})
|
||
|
||
test('Becken anlegen, bearbeiten und (leer) löschen', async ({ page }) => {
|
||
await gotoSection(page, de.nav.enclosures)
|
||
await page.getByRole('link', { name: tb.newButton }).click()
|
||
|
||
// Pflichtfeld-Validierung
|
||
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||
await expect(page.getByText(tb.validation.nameRequired)).toBeVisible()
|
||
|
||
const name = uniqueName('Becken')
|
||
await page.getByLabel(`${tb.fields.name} *`).fill(name)
|
||
await page.getByLabel(tb.fields.notes).fill('Übergangsbecken')
|
||
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||
await expect(page.getByText(tb.occupancy.empty)).toBeVisible()
|
||
|
||
// Leeres Becken löschen -> zurück zur Liste
|
||
acceptNextDialog(page)
|
||
await page.getByRole('button', { name: tb.delete.action }).click()
|
||
await expect(page.getByRole('heading', { name: tb.title, exact: true })).toBeVisible()
|
||
await expect(page.getByRole('link', { name })).toBeHidden()
|
||
})
|
||
})
|
||
|
||
test.describe('Kontakte', () => {
|
||
test('Detail zeigt verknüpfte Tiere mit Rollen-Beschriftung', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/kontakte')
|
||
await page.getByRole('link', { name: /Zoohandlung Meier/ }).click()
|
||
await expect(page.getByRole('heading', { name: 'Zoohandlung Meier' })).toBeVisible()
|
||
await expect(page.getByText(tk.linked.title)).toBeVisible()
|
||
// "Herkunft von Krümel"
|
||
await expect(page.getByText(`${tk.linked.originRole} Krümel`)).toBeVisible()
|
||
})
|
||
|
||
test('Löschen eines referenzierten Kontakts meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/kontakte')
|
||
await page.getByRole('link', { name: /Familie Huber/ }).click()
|
||
// Pauli (abgegeben) referenziert Familie Huber als Abnehmer
|
||
await expect(page.getByText(`${tk.linked.receiverRole} Pauli`)).toBeVisible()
|
||
acceptNextDialog(page)
|
||
await page.getByRole('button', { name: tk.delete.action }).click()
|
||
await expect(page.getByText(tk.delete.conflict)).toBeVisible()
|
||
})
|
||
|
||
test('Kontakt anlegen und (unverknüpft) wieder löschen', async ({ page }) => {
|
||
await gotoSection(page, de.nav.contacts)
|
||
// Explizit auf die Listenüberschrift warten, bevor wir klicken — verhindert den
|
||
// CI-Flake, der auftrat wenn der Vite-Server unter paralleler Last noch nicht
|
||
// alle Routen der Seite gerendert hatte (gotoSection klickt nur den Nav-Link,
|
||
// wartet aber nicht auf den vollständigen Seitenaufbau).
|
||
await expect(page.getByRole('heading', { name: tk.title, exact: true })).toBeVisible()
|
||
await page.getByRole('link', { name: tk.newButton }).click()
|
||
await expect(page.getByRole('heading', { name: tk.form.createTitle })).toBeVisible()
|
||
const name = uniqueName('Kontakt')
|
||
await page.getByLabel(`${tk.fields.name} *`).fill(name)
|
||
// FEAT-13: strukturierte Felder statt Freitext-Kontaktdaten
|
||
await page.getByLabel(tk.fields.email).fill('test@example.de')
|
||
await page.getByLabel(tk.fields.phone).fill('0151 9876543')
|
||
await page.getByRole('button', { name: tk.form.save, exact: true }).click()
|
||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||
await expect(page.getByText(tk.linked.empty)).toBeVisible()
|
||
// die gespeicherten Felder erscheinen auf der Detailseite
|
||
await expect(page.getByText('test@example.de')).toBeVisible()
|
||
await expect(page.getByText('0151 9876543')).toBeVisible()
|
||
|
||
acceptNextDialog(page)
|
||
await page.getByRole('button', { name: tk.delete.action }).click()
|
||
await expect(page.getByRole('heading', { name: tk.title, exact: true })).toBeVisible()
|
||
})
|
||
|
||
// FEAT-13: Abdeckung der neuen strukturierten Kontaktfelder
|
||
test('Detail zeigt E-Mail, Telefon und Adresse mit deutschen Beschriftungen', async ({ page }) => {
|
||
skipUnlessMock()
|
||
await page.goto('/kontakte')
|
||
await page.getByRole('link', { name: /Zoohandlung Meier/ }).click()
|
||
await expect(page.getByText(tk.fields.email, { exact: true })).toBeVisible()
|
||
await expect(page.getByText('meier@example.de')).toBeVisible()
|
||
await expect(page.getByText(tk.fields.address, { exact: true })).toBeVisible()
|
||
await expect(page.getByText('Hauptstraße 1, 12345 Musterstadt')).toBeVisible()
|
||
})
|
||
})
|