Files
GerbilManager/gerbil-manager-web/e2e/stammbaum.spec.ts
Gulum a42966ad8e STAMMBAUM-EXPAND: +-Button prominenter + Mini-Legende (Stammbaum-UX)
Punkt 1 - Expand-Button sichtbar/zuverlaessig:
- 40px solid-fill (war 32px outline), box-shadow, flex-centered;
  deutlich sichtbar auf 190px Karte, touch-tauglich (>=40px Tap-Target).
  Hover/focus-visible: opacity + outline.
- Edge-case (Litter mit null-Eltern): +-Button bleibt sichtbar --
  unbekannt-Karten sind korrektes Feedback fuer quarantaenierte Eltern.

Punkt 2 - Bottom-Hint Mini-Legende:
- stammbaum-hint -> stammbaum-hints ul (flex-wrap, 3 Items):
  Umwurzeln / Name=Akte / +=Nachladen. Gut lesbar auf 390px.
  de.ts: hintName + hintExpand (neue Keys; tapHint bleibt fuer card title).

e2e: +2 Specs x 2 Viewports = 4 neue Tests (Legende + Expand-Klick).
Emil (Gen 4, litterId w-emil) liefert Testanker im Mock-Stammbaum.

Gate: vitest 122/122 e2e 166+68skipped build+tsc+eslint clean.
2026-06-07 00:38:46 +02:00

113 lines
5.0 KiB
TypeScript
Raw Permalink 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: Stammbaum (FEAT-4) — interaktiver Viewer, Re-Root, Druckansicht. */
import { de, expect, skipUnlessMock, test } from './fixtures'
const t = de.pages.stammbaum
test('Viewer rendert die Ahnen-Karten und re-rootet per Tipp aufs Elterntier', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
await expect(page.locator('.pedigree-card').first()).toBeVisible()
await expect(page.getByRole('heading', { name: t.titleFor('Krümel') })).toBeVisible()
// Erst die Ansicht einpassen, damit alle Karten im SVG-Viewport liegen
// (sonst tippt der Klick auf dem Laptop ins Leere).
const fit = page.getByRole('button', { name: t.zoomFit })
if (await fit.isVisible()) await fit.click()
// Tipp auf den Vater -> Stammbaum wurzelt neu auf Fridolin.
// (force: die d3-Baum-Animation bewegt die Karten noch eine Weile —
// Playwrights Stabilitäts-Check würde sonst dauerhaft warten.)
await page.waitForTimeout(900)
await page
.locator('.pedigree-card:not(.pedigree-card--root):not(.pedigree-card--unknown)')
.filter({ hasText: 'Fridolin' })
.first()
.click({ force: true })
await page.waitForURL('**/rennmaeuse/fridolin/stammbaum')
await expect(page.getByRole('heading', { name: t.titleFor('Fridolin') })).toBeVisible()
})
test('Inzuchtkoeffizient wird angezeigt', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
const inzucht = page.locator('.stammbaum-inzucht')
await expect(inzucht).toBeVisible()
await expect(inzucht).toContainText(t.inbreeding.label)
// 0.0625 -> de-DE mit max. 1 Nachkommastelle -> "6,3 %"
await expect(inzucht).toContainText('6,3')
})
test('Druckansicht (Ahnentafel) erscheint im Print-Medium', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
await expect(page.locator('.pedigree-card').first()).toBeVisible()
await page.emulateMedia({ media: 'print' })
// Spaltenüberschriften der Ahnentafel + Erstellt-am-Zeile
await expect(page.getByText(t.printView.generations[1], { exact: true })).toBeVisible()
await expect(page.getByText(new RegExp(t.printView.createdOn))).toBeVisible()
await page.emulateMedia({ media: 'screen' })
})
test('unbekanntes Tier zeigt den deutschen Nicht-gefunden-Zustand', async ({ page }) => {
await page.goto('/rennmaeuse/gibt-es-nicht/stammbaum')
await expect(page.getByText(t.notFound).or(page.getByText(de.api.errors.notFound))).toBeVisible()
})
test('Ahnen-Namen sind Links zur Tier-Detailseite (STAMMBAUM-OPEN)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
await expect(page.locator('.pedigree-card').first()).toBeVisible()
// Fridolin ist Vater von Krümel — sein Name-Link soll /rennmaeuse/fridolin öffnen
const nameLink = page.getByRole('link', { name: 'Fridolin' })
await expect(nameLink).toBeVisible()
// href korrekt gesetzt
await expect(nameLink).toHaveAttribute('href', '/rennmaeuse/fridolin')
// Tastatur-Navigation (umgeht Toolbar-Überlappung im SVG-Canvas, testet zugl. Accessibility)
await nameLink.focus()
await page.keyboard.press('Enter')
await expect(page.getByRole('heading', { name: 'Fridolin' })).toBeVisible()
await expect(page).toHaveURL(/\/rennmaeuse\/fridolin$/)
})
test('Namenloser Ahne zeigt Platzhalter in der Stammbaum-Karte (UI-POLISH-2)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/nameless-stub/stammbaum')
// Titel zeigt '(ohne Namen)'
await expect(page.getByRole('heading', { name: t.titleFor(de.pages.gerbils.nameless) })).toBeVisible()
// Karte selbst zeigt '(ohne Namen)' statt leer
await expect(page.locator('.pedigree-card__nametext')).toHaveText(de.pages.gerbils.nameless)
})
test('Mini-Legende zeigt alle drei Interaktionshinweise (STAMMBAUM-EXPAND)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
await expect(page.locator('.pedigree-card').first()).toBeVisible()
const hints = page.locator('.stammbaum-hints')
await expect(hints).toBeVisible()
await expect(hints).toContainText(t.tapHint)
await expect(hints).toContainText(t.hintName)
await expect(hints).toContainText(t.hintExpand)
})
test('-Knopf ist sichtbar und lädt weitere Vorfahren nach (STAMMBAUM-EXPAND)', async ({ page }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel/stammbaum')
await expect(page.locator('.pedigree-card').first()).toBeVisible()
// Einpassen, damit die 4. Generation (Emil mit ) im Viewport liegt.
const fit = page.getByRole('button', { name: t.zoomFit })
if (await fit.isVisible()) await fit.click()
await page.waitForTimeout(600)
// -Button muss an der Tiefengrenze erscheinen (Emil hat litterId w-emil).
const expandBtn = page.getByRole('button', { name: t.expand }).first()
await expect(expandBtn).toBeVisible()
// Klick auf : Emil wird aufgeklappt → Max (sein Vater) taucht als Link auf.
await expandBtn.click({ force: true })
await expect(page.getByRole('link', { name: 'Max' })).toBeVisible({ timeout: 8000 })
})