feat(ruecknahmen): zurückgenommene Tiere erfassen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -416,6 +416,42 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
return json(route, 405)
|
||||
}
|
||||
|
||||
// RÜCKNAHMEN: zurückgenommene Tiere (CRUD /returns, GET optional ?gerbilId=).
|
||||
// Eigene Route (kein Gridify-Paging — die API liefert ein flaches Array).
|
||||
const ret = path.match(/^\/returns(?:\/([^/]+))?$/)
|
||||
if (ret) {
|
||||
const retId = ret[1] ? decodeURIComponent(ret[1]) : null
|
||||
if (!retId) {
|
||||
if (method === 'GET') {
|
||||
const gid = url.searchParams.get('gerbilId')
|
||||
const rows = gid ? db.returns.filter((r) => r.gerbilId === gid) : db.returns
|
||||
return json(route, 200, [...rows].reverse())
|
||||
}
|
||||
if (method === 'POST') {
|
||||
const body = request.postDataJSON() as Row
|
||||
if (!body.gerbilId && !body.gerbilName) {
|
||||
return json(route, 400, 'Es muss ein Tier angegeben werden.')
|
||||
}
|
||||
const created = { id: newId('return'), ...body, createdAt: new Date().toISOString() }
|
||||
db.returns.push(created)
|
||||
return json(route, 201, created)
|
||||
}
|
||||
return json(route, 405)
|
||||
}
|
||||
const idx = db.returns.findIndex((r) => r.id === retId)
|
||||
if (method === 'PUT') {
|
||||
if (idx < 0) return json(route, 404, { title: 'Not Found' })
|
||||
Object.assign(db.returns[idx], request.postDataJSON() as Row)
|
||||
return json(route, 200, db.returns[idx])
|
||||
}
|
||||
if (method === 'DELETE') {
|
||||
if (idx < 0) return json(route, 404, { title: 'Not Found' })
|
||||
db.returns.splice(idx, 1)
|
||||
return json(route, 204)
|
||||
}
|
||||
return json(route, 405)
|
||||
}
|
||||
|
||||
// Generische Kollektionen: /<resource> und /<resource>/<id>
|
||||
m = path.match(/^\/([a-z-]+)(?:\/([^/]+))?$/)
|
||||
const col = m ? collections[m[1]] : undefined
|
||||
|
||||
@@ -78,6 +78,8 @@ export interface MockDb {
|
||||
namesConfigured: boolean
|
||||
// FEEDBACK: "Fehler melden" — gesammelte Berichte (POST /feedback)
|
||||
feedback: Record<string, unknown>[]
|
||||
// RÜCKNAHMEN: zurückgenommene Tiere (CRUD /returns)
|
||||
returns: Record<string, unknown>[]
|
||||
}
|
||||
|
||||
function gerbil(
|
||||
@@ -376,5 +378,6 @@ export function seedDb(): MockDb {
|
||||
saleAdConfigured: true,
|
||||
namesConfigured: true,
|
||||
feedback: [],
|
||||
returns: [],
|
||||
}
|
||||
}
|
||||
|
||||
59
gerbil-manager-web/e2e/ruecknahmen.spec.ts
Normal file
59
gerbil-manager-web/e2e/ruecknahmen.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/** RÜCKNAHMEN: Verwaltungsseite für zurückgekommene Tiere (/ruecknahmen). */
|
||||
import { de, expect, skipUnlessMock, test } from './fixtures'
|
||||
|
||||
const t = de.pages.ruecknahmen
|
||||
|
||||
test('Rücknahmen: Liste ist anfangs leer und das Formular lässt sich öffnen', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/ruecknahmen')
|
||||
|
||||
await expect(page.getByRole('heading', { name: t.title })).toBeVisible()
|
||||
await expect(page.getByText(t.empty)).toBeVisible()
|
||||
|
||||
await page.getByRole('button', { name: t.newButton }).click()
|
||||
await expect(page.getByRole('heading', { name: t.formTitle })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Rücknahmen: erfassen über Formular speichert und zeigt den Eintrag', async ({ page, mockDb }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/ruecknahmen')
|
||||
|
||||
await page.getByRole('button', { name: t.newButton }).click()
|
||||
|
||||
// Tier + Kontakt aus den Dropdowns wählen, Datum + Grund eintragen.
|
||||
await page.getByRole('combobox', { name: t.fields.gerbil, exact: true }).selectOption({ label: 'Krümel' })
|
||||
await page.getByRole('textbox', { name: t.fields.returnDate, exact: true }).fill('2026-05-01')
|
||||
await page.getByRole('combobox', { name: t.fields.fromContact, exact: true }).selectOption({ label: 'Zoohandlung Meier' })
|
||||
await page.getByRole('textbox', { name: t.fields.note, exact: true }).fill('Allergie in der Familie')
|
||||
|
||||
await page.getByRole('button', { name: t.save }).click()
|
||||
|
||||
// Eintrag erscheint in der Liste mit verlinktem Tiernamen.
|
||||
const card = page.locator('.gerbil-card', { hasText: 'Krümel' })
|
||||
await expect(card).toBeVisible()
|
||||
await expect(card).toContainText('Allergie in der Familie')
|
||||
await expect(card.getByRole('link', { name: 'Krümel' })).toBeVisible()
|
||||
|
||||
// Im Mock persistiert (gerbilId + note).
|
||||
expect(mockDb).not.toBeNull()
|
||||
const rows = mockDb!.returns
|
||||
expect(rows.length).toBe(1)
|
||||
expect(rows[0]).toMatchObject({ gerbilId: 'kruemel', note: 'Allergie in der Familie' })
|
||||
})
|
||||
|
||||
test('Rücknahmen: Eintrag löschen entfernt ihn aus der Liste', async ({ page, mockDb }) => {
|
||||
skipUnlessMock()
|
||||
// Einen Eintrag über die UI anlegen (Fixture erstellt die DB frisch pro Test).
|
||||
await page.goto('/ruecknahmen')
|
||||
await page.getByRole('button', { name: t.newButton }).click()
|
||||
await page.getByRole('combobox', { name: t.fields.gerbil, exact: true }).selectOption({ label: 'Krümel' })
|
||||
await page.getByRole('button', { name: t.save }).click()
|
||||
await expect(page.locator('.gerbil-card', { hasText: 'Krümel' })).toBeVisible()
|
||||
|
||||
// Löschen bestätigen.
|
||||
page.on('dialog', (d) => d.accept())
|
||||
await page.locator('.gerbil-card', { hasText: 'Krümel' }).getByRole('button', { name: t.delete }).click()
|
||||
|
||||
await expect(page.getByText(t.empty)).toBeVisible()
|
||||
expect(mockDb!.returns.length).toBe(0)
|
||||
})
|
||||
Reference in New Issue
Block a user