Files
GerbilManager/gerbil-manager-web/e2e/tiere.spec.ts
Gulum 6eb82da90c UX-MOBILE-1: FilterPanel — einklappbare Filter auf Mobil (390px)
Neues FilterPanel-Muster (src/components/FilterPanel.tsx + filterPanel.css):
- Mobil (<768px): Suchfeld immer sichtbar; alle weiteren Filter hinter einem
  'Filter (N)'-Button eingeklappt (Badge = Anzahl aktiver Nicht-Default-Filter).
  Tippen öffnet/schließt den Drawer. 'Filter zurücksetzen' im Drawer.
- Desktop (>=768px): Toggle versteckt, Drawer als display:contents → alle Controls
  inline in der bestehenden .filters-Flexzeile, wie bisher.
Auf GerbilsPage, WuerfeListPage und AnfragenPage ausgerollt.
BeckenPage/KontaktePage nur ein Suchfeld → kein Panel nötig.

e2e: filter-panel.spec.ts (8 Tests, phone+desktop); bestand/tiere/anfragen-Specs
erhalten openFilterPanel-Aufruf vor Drawer-Controls; fixtures.ts +openFilterPanel().
Vitest 78 / e2e 102 grün.

Mobil-Pass (390px, Durchsicht): Hauptproblem behoben. Beobachtete Restpunkte
für god (nicht selbst gefixed):
- .genotype-table in Genetik/Probeverpaarung: Genotyp-Strings können eng werden
- Stammbaum (react-d3-tree SVG): kein explizites mobile Viewport-Handling
- BreedingResultView Genotyp-Detail-Tabelle: ggf. horizontal scrollbar-los

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 15:28:13 +02:00

110 lines
5.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('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()
})