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>
90 lines
3.9 KiB
TypeScript
90 lines
3.9 KiB
TypeScript
/**
|
|
* INBOX-1: Anfragen-Posteingang — Liste/Filter, Triage, KI-Entwurf (inkl.
|
|
* AiKeyMissing-Hinweis), Senden (inkl. MailNotConfigured-Hinweis).
|
|
* Mock-gebunden (Seed-Anfragen + Fehlerpfad-Flags) → skipUnlessMock.
|
|
*/
|
|
import { acceptNextDialog, de, expect, gotoSection, openFilterPanel, skipUnlessMock, test } from './fixtures'
|
|
|
|
const ta = de.pages.anfragen
|
|
const td = ta.detail
|
|
|
|
test.describe('Anfragen', () => {
|
|
test('Liste zeigt Anfragen neueste zuerst + Status-Filter', async ({ page }) => {
|
|
skipUnlessMock()
|
|
await gotoSection(page, de.nav.requests)
|
|
await expect(page.getByRole('heading', { name: ta.title })).toBeVisible()
|
|
|
|
// Neueste zuerst: Anna (05.06.) vor Ben (04.06.) vor Clara (01.06.)
|
|
const cards = page.locator('.anfrage-card')
|
|
await expect(cards).toHaveCount(3)
|
|
await expect(cards.nth(0)).toContainText('Anna Albrecht')
|
|
await expect(cards.nth(2)).toContainText('clara@example.com') // ohne Anzeigename → Adresse
|
|
|
|
// Status-Badge auf der Karte
|
|
await expect(cards.nth(0)).toContainText(ta.statusLabels.New)
|
|
|
|
// UX-MOBILE-1: Status-Select liegt im Filter-Drawer — auf Mobil erst öffnen.
|
|
await openFilterPanel(page)
|
|
// Filter: nur Beantwortet
|
|
await page.getByLabel(td.statusLabel).selectOption('Answered')
|
|
await expect(cards).toHaveCount(1)
|
|
await expect(cards.first()).toContainText('Danke!')
|
|
})
|
|
|
|
test('Sync ohne Gmail-Konfiguration zeigt deutschen Hinweis', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
mockDb!.mailConfigured = false
|
|
await page.goto('/anfragen')
|
|
await page.getByRole('button', { name: ta.sync }).click()
|
|
await expect(page.getByText(ta.mailNotConfigured)).toBeVisible()
|
|
})
|
|
|
|
test('Detail: Triage — Abnehmer zuordnen setzt Status auf Zugeordnet', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/anfragen/req-anna')
|
|
await expect(page.getByRole('heading', { name: 'Anfrage: Pärchen zur Abgabe?' })).toBeVisible()
|
|
await expect(page.getByText('anna@example.de', { exact: false })).toBeVisible()
|
|
|
|
await page.getByLabel(td.assignContact).selectOption({ label: 'Zoohandlung Meier' })
|
|
await expect(page.locator('.anfrage-badge--assigned')).toBeVisible()
|
|
expect(mockDb!.requests.find((r) => r.id === 'req-anna')!.assignedContactId).toBe('con-meier')
|
|
|
|
// Verwerfen (mit Bestätigung) → Status Verworfen, Zuordnung bleibt
|
|
acceptNextDialog(page)
|
|
await page.getByRole('button', { name: td.abandon }).click()
|
|
await expect(page.locator('.anfrage-badge--abandoned')).toBeVisible()
|
|
expect(mockDb!.requests.find((r) => r.id === 'req-anna')!.assignedContactId).toBe('con-meier')
|
|
})
|
|
|
|
test('KI-Entwurf füllt das Antwortfeld; Senden markiert als Beantwortet', async ({ page }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/anfragen/req-anna')
|
|
|
|
await page.getByRole('button', { name: td.draftButton }).click()
|
|
const textarea = page.locator('.anfrage-reply__text')
|
|
await expect(textarea).toHaveValue(/vielen Dank für deine Anfrage/)
|
|
|
|
// Entwurf bearbeiten, dann senden (bestätigt) → Beantwortet + Hinweis
|
|
await textarea.fill('Hallo Anna, ja — die beiden suchen noch ein Zuhause!')
|
|
acceptNextDialog(page)
|
|
await page.getByRole('button', { name: td.send }).click()
|
|
await expect(page.getByText(td.sent)).toBeVisible()
|
|
await expect(page.locator('.anfrage-badge--answered')).toBeVisible()
|
|
})
|
|
|
|
test('KI ohne Schlüssel: 503 wird zum freundlichen Hinweis', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
mockDb!.aiConfigured = false
|
|
await page.goto('/anfragen/req-anna')
|
|
await page.getByRole('button', { name: td.draftButton }).click()
|
|
await expect(page.getByText(td.aiKeyMissing)).toBeVisible()
|
|
})
|
|
|
|
test('Senden ohne Text zeigt Hinweis statt Versand', async ({ page }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/anfragen/req-anna')
|
|
await page.getByRole('button', { name: td.send }).click()
|
|
await expect(page.getByText(td.sendEmptyBody)).toBeVisible()
|
|
})
|
|
})
|