- de.ts: nameless = '(ohne Namen)' (neuer Platzhalter-String) - GerbilsPage: g.name || t.nameless in Karten-Name + Checkbox-Aria - GerbilDetailPage: g.name || nameless im h2-Header; g.causeOfDeath/notes via || null (leer-string-sicher wie Herkunft) - AnimalPicker: g.name || nameless im Picker-Button e2e: +1 nameless-stub in mock-data; 2 neue Tests (Liste + Detail-Header) Gate: vitest 82/82, e2e 130/130, tsc clean
152 lines
7.4 KiB
TypeScript
152 lines
7.4 KiB
TypeScript
/** QA-1: Tiere (Rennmäuse) — Liste/Suche, CRUD, Detail-Tabs (FEAT-1 + FEAT-6). */
|
|
import { de, expect, gotoSection, openFilterPanel, 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()
|
|
|
|
// UX-MOBILE-1: Herkunft-Select liegt im Filter-Drawer — auf Mobil erst öffnen.
|
|
await openFilterPanel(page)
|
|
// Herkunft (originBreeder) auf die Seed-Zucht 'Clan-Kleine-Chaoten' (nur Krümel).
|
|
await page
|
|
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.origin}")`) })
|
|
.locator('select')
|
|
.selectOption({ label: '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.getByLabel(`${t.fields.gender} *`).selectOption({ label: t.genderLabels.female })
|
|
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')
|
|
await page.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: 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('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()
|
|
})
|