Filter-UX aus dem Design: - Quick-Chip-Kategorien (Status/Geschlecht/Farbschlag/Herkunft) öffnen ein Bottom-Sheet mit Mehrfachauswahl (suchbar bei Farbschlag/Herkunft); aktive Filter als entfernbare Chips; Sortierung + "Externe Ahnen" als gleich große Chips (Sortierung öffnet dasselbe Drawer). - Neue zweizeilige Tierkarte (Name + Geschlechts-Kreis + Status-Badge, Meta-Zeile), cardIn/Chip/Sheet-Animationen, Titel blendet beim Scrollen aus. App-weit: - Design-Palette (Creme/Braun/Terrakotta), Hanken-Grotesk-Schrift und gemeinsamer --shadow-Token; obere "Rennmaus-Manager"-Titelleiste entfernt (mobil bleibt die untere Aktionsleiste); fixe Shell-Höhe, nur der Hauptbereich scrollt. Funktion: - Infinite Scroll via useInfiniteList (seitenweise) + IntersectionObserver-Sentinel. - Namensgenerator: Option männlich/weiblich/egal. - Mehrfach-OR-Filter über gridify orGroup; e2e an die neue UI angepasst. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
/**
|
|
* QA-1: Test-Fixtures.
|
|
*
|
|
* - MOCK-Modus (Standard): installiert vor jedem Test den In-Memory-Mock
|
|
* (frischer Datenbestand pro Test).
|
|
* - LIVE-Modus (E2E_BASE_URL gesetzt): kein Mock; fixture-gebundene Tests
|
|
* überspringen sich über `skipUnlessMock()`.
|
|
*
|
|
* Deutsche Oberflächen-Texte werden DIREKT aus src/strings/de.ts importiert —
|
|
* die Suite bleibt damit automatisch synchron zur App.
|
|
*/
|
|
import { test as base, expect, type Page } from '@playwright/test'
|
|
import { de } from '../src/strings/de'
|
|
import { installMockApi } from './mock-api'
|
|
import type { MockDb } from './mock-data'
|
|
|
|
export const LIVE = Boolean(process.env.E2E_BASE_URL)
|
|
|
|
export { expect }
|
|
export { de }
|
|
|
|
interface Fixtures {
|
|
/** Im Mock-Modus der pro Test frische Datenbestand, sonst null. */
|
|
mockDb: MockDb | null
|
|
}
|
|
|
|
export const test = base.extend<Fixtures>({
|
|
mockDb: [
|
|
async ({ page }, use) => {
|
|
const db = LIVE ? null : await installMockApi(page)
|
|
await use(db)
|
|
},
|
|
{ auto: true },
|
|
],
|
|
})
|
|
|
|
/** Test überspringen, wenn er Seed-Daten des Mocks voraussetzt. */
|
|
export function skipUnlessMock() {
|
|
test.skip(LIVE, 'benötigt die Mock-Seed-Daten (läuft nicht im LIVE-Modus)')
|
|
}
|
|
|
|
/**
|
|
* Test überspringen im Mock-Modus — nur im LIVE-Modus sinnvoll.
|
|
* Live-Tests MÜSSEN Ergebnisse assertieren (z. B. Liste nicht leer,
|
|
* konkreter Wert sichtbar), nicht nur HTTP-200. Lesson: der Mock spiegelt
|
|
* den Frontend-Code, nicht das echte Backend — ein Bug bleibt unsichtbar,
|
|
* wenn wir nur testen, ob die Seite lädt.
|
|
*/
|
|
export function skipUnlessLive() {
|
|
test.skip(!LIVE, 'nur im LIVE-Modus sinnvoll (E2E_BASE_URL setzen)')
|
|
}
|
|
|
|
/**
|
|
* Zu einem Navigationsziel wechseln — auf dem Smartphone liegen Becken,
|
|
* Kontakte und Statistik hinter dem „Mehr“-Blatt.
|
|
*/
|
|
export async function gotoSection(page: Page, label: string) {
|
|
const nav = page.getByRole('navigation', { name: de.nav.mainNavigation })
|
|
// App noch nicht geladen (about:blank) -> erst zur Startseite
|
|
if (!(await nav.isVisible())) {
|
|
await page.goto('/')
|
|
await expect(nav).toBeVisible()
|
|
}
|
|
const link = nav.getByRole('link', { name: label })
|
|
if (!(await link.isVisible())) {
|
|
await nav.getByRole('button', { name: de.nav.more }).click()
|
|
await expect(link).toBeVisible()
|
|
}
|
|
await link.click()
|
|
}
|
|
|
|
/** window.confirm des nächsten Dialogs annehmen. */
|
|
export function acceptNextDialog(page: Page) {
|
|
page.once('dialog', (d) => void d.accept())
|
|
}
|
|
|
|
/**
|
|
* UX-MOBILE-1: Filter-Drawer öffnen, falls der Toggle-Button sichtbar ist
|
|
* (= Smartphone-Ansicht). Auf Desktop-Ansicht ist er per CSS versteckt, dann
|
|
* kein Klick nötig — Controls sind direkt sichtbar.
|
|
*/
|
|
export async function openFilterPanel(page: Page) {
|
|
const toggle = page.locator('.filter-panel__toggle')
|
|
if (await toggle.isVisible()) {
|
|
await toggle.click()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DESIGN (Rennmaus Filter): wählt einen Wert im Quick-Chips-Filter der Rennmäuse-Liste.
|
|
* Öffnet das Bottom-Sheet der Kategorie, hakt die Option an und schließt mit „Fertig".
|
|
*/
|
|
export async function selectListFilter(page: Page, categoryLabel: string, optionLabel: string) {
|
|
// Pille der Kategorie in der Quick-Chips-Reihe öffnen (nicht den aktiven Chip treffen).
|
|
await page.locator('.rmf-quickrow').getByRole('button', { name: categoryLabel }).click()
|
|
const sheet = page.getByRole('dialog', { name: categoryLabel })
|
|
await sheet.getByRole('button', { name: optionLabel, exact: true }).click()
|
|
await sheet.getByRole('button', { name: de.filterBar.done }).click()
|
|
}
|
|
|
|
/** Eindeutiger Name für LIVE-taugliche Create-Flows. */
|
|
export const uniqueName = (prefix: string) =>
|
|
`${prefix} E2E ${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
|