Files
GerbilManager/gerbil-manager-web/e2e/tiere.spec.ts

229 lines
12 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.
/** QA-1: Tiere (Rennmäuse) — Liste/Suche, CRUD, Detail-Tabs (FEAT-1 + FEAT-6). */
import { de, expect, gotoSection, selectListFilter, skipUnlessMock, test, uniqueName } from './fixtures'
const t = de.pages.gerbils
const tabs = de.pages.tierTabs
test('Liste lädt und die Namenssuche filtert', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse')
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeVisible()
await page.getByPlaceholder(t.searchPlaceholder).first().fill('Krüm')
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden()
})
test('Herkunft-Filter (originBreeder) zeigt nur Tiere der gewählten Zucht (SEARCH-2b)', async ({
page,
}) => {
skipUnlessMock()
await page.goto('/rennmaeuse')
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeVisible()
// DESIGN (Rennmaus Filter): Herkunft (originBreeder) über das Quick-Chips-Sheet
// auf die Seed-Zucht 'Clan-Kleine-Chaoten' (nur Krümel) filtern.
await selectListFilter(page, t.fields.origin, 'Clan-Kleine-Chaoten')
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden()
})
test('Namenssuche ist trennzeichen-unempfindlich (SEARCH-2b)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse')
// 'Krü mel' (mit Leerzeichen) wird normalisiert zu 'krümel' -> matcht Krümel.
await page.getByPlaceholder(t.searchPlaceholder).first().fill('Krü mel')
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden()
})
test('Neue Rennmaus anlegen — Validierung + Erfolg', async ({ page }) => {
await gotoSection(page, de.nav.gerbils)
await page.getByRole('link', { name: t.newButton }).click()
await expect(page.getByRole('heading', { name: t.form.createTitle })).toBeVisible()
// Pflichtfeld-Validierung auf Deutsch
await page.getByRole('button', { name: t.form.save, exact: true }).click()
await expect(page.getByText(t.validation.nameRequired)).toBeVisible()
await expect(page.getByText(t.validation.genderRequired)).toBeVisible()
const name = uniqueName('Testmaus')
await page.getByLabel(`${t.fields.name} *`).fill(name)
await page.getByRole('button', { name: t.genderLabels.female, exact: false }).click()
await page.getByRole('button', { name: t.form.save, exact: true }).click()
// Erfolg: Detailseite des neuen Tieres
await expect(page.getByRole('heading', { name })).toBeVisible()
await expect(page.getByText(t.detail.masterData)).toBeVisible()
})
test('Detailseite zeigt Stammdaten + Tab-Inhalte (Gesundheit/Gewicht/Fotos)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel')
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.getByText(t.detail.masterData)).toBeVisible()
// Fotos (Standard-Tab): leerer Zustand + Datei-Pflicht-Validierung
await expect(page.getByText(tabs.photos.empty)).toBeVisible()
await page.getByRole('button', { name: tabs.photos.uploadButton }).click()
await expect(page.getByText(tabs.photos.validation.fileRequired)).toBeVisible()
// Gesundheit: Seed-Eintrag sichtbar, neuen Eintrag anlegen
await page.getByRole('tab', { name: t.detail.tabs.health }).click()
await expect(page.getByText('Jahresimpfung')).toBeVisible()
await expect(page.getByText(tabs.health.typeLabels.Vaccination, { exact: true })).toBeVisible()
await page.getByRole('button', { name: tabs.health.newButton }).click()
// 'Art' exakt ansteuern ('Tierarzt' enthält 'arzt'; Label-Text enthält die Optionen)
await page
.locator('label.field', { has: page.locator(`span:text-is("${tabs.health.fields.type}")`) })
.locator('select')
.selectOption({ label: tabs.health.typeLabels.Examination })
await page.getByLabel(`${tabs.health.fields.description} *`).fill('Routinekontrolle, alles gut')
await page.getByRole('button', { name: tabs.actions.save, exact: true }).click()
await expect(page.getByText('Routinekontrolle, alles gut')).toBeVisible()
// Gewicht: Verlaufskurve (2 Seed-Einträge) + Schnell-Erfassung
await page.getByRole('tab', { name: t.detail.tabs.weight }).click()
await expect(page.locator('svg.line-chart')).toBeVisible()
await expect(page.getByText(/78\s*g/)).toBeVisible()
await page.getByLabel(tabs.weight.fields.weightGrams).fill('85')
// Im Tabpanel ansprechen — die Erwerb-Sektion der Akte hat ebenfalls einen „Hinzufügen"-Button.
await page.getByRole('tabpanel').getByRole('button', { name: tabs.weight.addButton, exact: true }).click()
await expect(page.getByText(/85\s*g/)).toBeVisible()
})
test('Detailseite: Wurf-Feld ist ein Link zur Wurf-Detailseite (WURF-LINK)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel')
// Krümel hat litterId 'w-kruemel' → Name 'Wurf K' → Link /wuerfe/w-kruemel
const litterLink = page.getByRole('link', { name: 'Wurf K' })
await expect(litterLink).toBeVisible()
await litterLink.click()
await expect(page.getByRole('heading', { name: 'Wurf K' })).toBeVisible()
})
test('Detailseite zeigt Geschwisterverpaarungs-Hinweis nur wenn die Eltern Vollgeschwister sind (SIBLING-PAIRING)', async ({ page }) => {
skipUnlessMock()
// 'Inzucht Kind': Eltern Zwilling Bock + Zwilling Maus aus demselben Wurf
// 'w-zwillinge' → Vollgeschwister → Hinweis sichtbar.
await page.goto('/rennmaeuse/inzucht-kind')
await expect(page.getByRole('heading', { name: 'Inzucht Kind' })).toBeVisible()
await expect(page.locator('.ak-fact--sibling')).toContainText(t.detail.siblingPairing)
// 'Krümel': Eltern Fridolin × Luna aus verschiedenen Würfen → kein Hinweis.
await page.goto('/rennmaeuse/kruemel')
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.locator('.ak-fact--sibling')).toHaveCount(0)
})
test('Detailseite: Herkunft zeigt originBreeder als Text wenn kein Kontakt (WURF-LINK-Addendum)', async ({ page }) => {
skipUnlessMock()
// Fridolin hat originContactId=null + originBreeder='Zoohandlung Meier' → plain text, kein Link
await page.goto('/rennmaeuse/fridolin')
await expect(page.getByRole('heading', { name: 'Fridolin' })).toBeVisible()
await expect(page.getByText('Zoohandlung Meier')).toBeVisible()
// Kein Link mit diesem Namen — es ist reiner Text
await expect(page.getByRole('link', { name: 'Zoohandlung Meier' })).toBeHidden()
})
test('Detailseite: Herkunft zeigt Kontaktlink wenn originContactId gesetzt (WURF-LINK-Addendum)', async ({ page }) => {
skipUnlessMock()
// Krümel hat originContactId='con-meier' → Link /kontakte/con-meier (Priorität vor originBreeder)
await page.goto('/rennmaeuse/kruemel')
const originLink = page.getByRole('link', { name: 'Zoohandlung Meier' })
await expect(originLink).toBeVisible()
await expect(originLink).toHaveAttribute('href', '/kontakte/con-meier')
})
test('Detailseite: externer Vorfahre (isResident=false) blendet Würfe & Charakter aus (#17/#20)', async ({ page }) => {
skipUnlessMock()
// 'hilde' ist isResident=false (externer Ahne, nur für den Stammbaum erfasst).
await page.goto('/rennmaeuse/hilde')
await expect(page.getByRole('heading', { name: 'Hilde' })).toBeVisible()
// Extern-Badge + Hinweis sichtbar, Charakter- und Würfe-Abschnitt ausgeblendet.
await expect(page.getByText(t.detail.nonResidentNote)).toBeVisible()
await expect(page.getByText(de.character.sectionTitle)).toBeHidden()
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeHidden()
// Gegenprobe: Bestandstier Krümel zeigt beide Abschnitte.
await page.goto('/rennmaeuse/kruemel')
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.getByText(de.character.sectionTitle)).toBeVisible()
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
await expect(page.getByText(t.detail.nonResidentNote)).toBeHidden()
})
test('Namenlose Tiere zeigen Platzhalter in der Liste (UI-POLISH-1)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse')
// 'nameless-stub' hat name='' → Platzhalter '(ohne Namen)' soll als Link sichtbar sein
await expect(page.getByRole('link', { name: de.pages.gerbils.nameless })).toBeVisible()
})
test('Namenlose Tiere zeigen Platzhalter im Detail-Header (UI-POLISH-1)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/nameless-stub')
await expect(page.getByRole('heading', { name: de.pages.gerbils.nameless })).toBeVisible()
})
test('Tier bearbeiten — Notizen ändern', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/bearbeiten')
await expect(page.getByRole('heading', { name: t.form.editTitle })).toBeVisible()
await page.getByLabel(t.fields.notes).fill('Sehr zutraulich.')
await page.getByRole('button', { name: t.form.save, exact: true }).click()
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
await expect(page.getByText('Sehr zutraulich.')).toBeVisible()
})
test('Tier bearbeiten — originBreeder Freitext round-trip (FORM-FIELDS-1)', async ({ page }) => {
skipUnlessMock()
// Fridolin hat originBreeder='Zoohandlung Meier', kein originContactId → Freitext-Feld vorbelegt
await page.goto('/rennmaeuse/fridolin/bearbeiten')
await expect(page.getByLabel(t.fields.originBreeder)).toHaveValue('Zoohandlung Meier')
await page.getByLabel(t.fields.originBreeder).fill('Neue Musterzucht')
await page.getByRole('button', { name: t.form.save, exact: true }).click()
// Detailseite: Herkunft zeigt den neuen Wert (kein Contact → originBreeder-Text)
await expect(page.getByText('Neue Musterzucht')).toBeVisible()
})
test('Tier bearbeiten — isDeaf Tristate round-trip (FORM-FIELDS-1)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/bearbeiten')
// Standard: Unbekannt (null → value='')
await expect(page.getByLabel(t.fields.isDeaf)).toHaveValue('')
await page.getByLabel(t.fields.isDeaf).selectOption({ label: t.form.isDeafYes })
await page.getByRole('button', { name: t.form.save, exact: true }).click()
// Zurück zur Bearbeiten-Seite: Wert muss erhalten sein
await page.goto('/rennmaeuse/kruemel/bearbeiten')
await expect(page.getByLabel(t.fields.isDeaf)).toHaveValue('true')
})
test('Detailseite: Würfe als Elternteil zeigt verlinkten Wurf (ANIMAL-LITTERS)', async ({ page }) => {
skipUnlessMock()
// Fridolin ist fatherId von 'w-kruemel' (Wurf K)
await page.goto('/rennmaeuse/fridolin')
await expect(page.getByRole('heading', { name: 'Fridolin' })).toBeVisible()
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
// Wurf K als Link sichtbar
const litterLink = page.getByRole('link', { name: /Wurf K/ })
await expect(litterLink).toBeVisible()
// Vater-Rollen-Badge im Wurf-Eintrag sichtbar (die Akte zeigt „Vater" jetzt auch
// als Stammdaten-Zeile, daher gezielt auf das Badge eingrenzen).
await expect(page.locator('.badge', { hasText: t.detail.parentLittersRoleVater })).toBeVisible()
// Navigation zum Wurf funktioniert
await litterLink.first().click()
await expect(page.getByRole('heading', { name: 'Wurf K' })).toBeVisible()
})
test('Detailseite: Keine Würfe als Elternteil zeigt Leer-Zustand (ANIMAL-LITTERS)', async ({ page }) => {
skipUnlessMock()
// Krümel hat keine Würfe als Elternteil (nur als Kind von w-kruemel)
await page.goto('/rennmaeuse/kruemel')
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
await expect(page.getByText(t.detail.parentLittersEmpty)).toBeVisible()
})