FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils
This commit is contained in:
@@ -7,9 +7,6 @@ test('Farbschlag-Auswahl füllt Gencode automatisch aus', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/rennmaeuse/neu')
|
||||
|
||||
const farbschlagSelect = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.colorVariety}")`) })
|
||||
.locator('select')
|
||||
const genotypeInput = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.genotype}")`) })
|
||||
.locator('input')
|
||||
@@ -18,7 +15,8 @@ test('Farbschlag-Auswahl füllt Gencode automatisch aus', async ({ page }) => {
|
||||
await expect(genotypeInput).toHaveValue('')
|
||||
|
||||
// 'Agouti' wählen → Gencode wird automatisch befüllt.
|
||||
await farbschlagSelect.selectOption({ label: 'Agouti' })
|
||||
await page.locator('.color-picker-trigger').click()
|
||||
await page.locator('.color-picker-option', { hasText: 'Agouti' }).click()
|
||||
await expect(genotypeInput).toHaveValue('AA CC DD EE GG PP spsp rere')
|
||||
})
|
||||
|
||||
@@ -26,54 +24,47 @@ test('Gencode-Eingabe aktualisiert die Farbschlag-Auswahl', async ({ page }) =>
|
||||
skipUnlessMock()
|
||||
await page.goto('/rennmaeuse/neu')
|
||||
|
||||
const farbschlagSelect = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.colorVariety}")`) })
|
||||
.locator('select')
|
||||
const genotypeInput = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.genotype}")`) })
|
||||
.locator('input')
|
||||
|
||||
// Gencode für Schwarz eintippen → Farbschlag-Select springt auf 'Schwarz'.
|
||||
await genotypeInput.fill('aa CC DD EE GG PP spsp rere')
|
||||
await expect(farbschlagSelect).toHaveValue('cv-schwarz')
|
||||
await expect(page.locator('.color-picker-trigger')).toHaveText(/Schwarz/)
|
||||
})
|
||||
|
||||
test('Gencode ohne passende Farbschlag-Auswahl leert das Dropdown', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/rennmaeuse/neu')
|
||||
|
||||
const farbschlagSelect = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.colorVariety}")`) })
|
||||
.locator('select')
|
||||
const genotypeInput = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.genotype}")`) })
|
||||
.locator('input')
|
||||
|
||||
// Zuerst eine bekannte Farbe wählen, damit das Select belegt ist.
|
||||
await farbschlagSelect.selectOption({ label: 'Agouti' })
|
||||
await expect(farbschlagSelect).not.toHaveValue('')
|
||||
await page.locator('.color-picker-trigger').click()
|
||||
await page.locator('.color-picker-option', { hasText: 'Agouti' }).click()
|
||||
await expect(page.locator('.color-picker-trigger')).toHaveText(/Agouti/)
|
||||
|
||||
// Marder-Genotyp: bekannte Farbe im Engine, aber NICHT im Mock-Dropdown
|
||||
// → Select wird auf '' (kein Eintrag) zurückgesetzt.
|
||||
await genotypeInput.fill('aa cchmcchm DD EE GG PP spsp rere')
|
||||
await expect(farbschlagSelect).toHaveValue('')
|
||||
await expect(page.locator('.color-picker-trigger')).toHaveText(/— keine Angabe —/)
|
||||
})
|
||||
|
||||
test('Unbekannter Genotyp leert das Farbschlag-Dropdown (Unbekannt-Fall)', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/rennmaeuse/neu')
|
||||
|
||||
const farbschlagSelect = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.colorVariety}")`) })
|
||||
.locator('select')
|
||||
const genotypeInput = page
|
||||
.locator('label.field', { has: page.locator(`span:text-is("${t.fields.genotype}")`) })
|
||||
.locator('input')
|
||||
|
||||
await farbschlagSelect.selectOption({ label: 'Blau' })
|
||||
await expect(farbschlagSelect).not.toHaveValue('')
|
||||
await page.locator('.color-picker-trigger').click()
|
||||
await page.locator('.color-picker-option', { hasText: 'Blau' }).click()
|
||||
await expect(page.locator('.color-picker-trigger')).toHaveText(/Blau/)
|
||||
|
||||
// aa CC dd EE gg pp → Unbekannter Farbschlag (nicht im Katalog) → Select leert sich.
|
||||
await genotypeInput.fill('aa CC dd EE gg pp spsp rere')
|
||||
await expect(farbschlagSelect).toHaveValue('')
|
||||
await expect(page.locator('.color-picker-trigger')).toHaveText(/— keine Angabe —/)
|
||||
})
|
||||
|
||||
@@ -45,12 +45,20 @@ function matchesFilter(row: Row, filter: string | null): boolean {
|
||||
|
||||
function applyOrderBy(rows: Row[], orderBy: string | null): Row[] {
|
||||
if (!orderBy) return rows
|
||||
const [field, dir] = orderBy.split(/\s+/)
|
||||
const sign = dir?.toLowerCase() === 'desc' ? -1 : 1
|
||||
// Mirror Gridify's comma-separated multi-key ordering (e.g. "name,id"): each key
|
||||
// is "<field> [desc]"; later keys act as tiebreakers.
|
||||
const keys = orderBy.split(',').map((part) => {
|
||||
const [field, dir] = part.trim().split(/\s+/)
|
||||
return { field, sign: dir?.toLowerCase() === 'desc' ? -1 : 1 }
|
||||
})
|
||||
return [...rows].sort((a, b) => {
|
||||
const av = String(a[field] ?? '')
|
||||
const bv = String(b[field] ?? '')
|
||||
return av < bv ? -sign : av > bv ? sign : 0
|
||||
for (const { field, sign } of keys) {
|
||||
const av = String(a[field] ?? '')
|
||||
const bv = String(b[field] ?? '')
|
||||
if (av < bv) return -sign
|
||||
if (av > bv) return sign
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -136,8 +136,10 @@ export function seedDb(): MockDb {
|
||||
{ ...gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), isResident: false, originBreeder: 'Zoohandlung Meier' },
|
||||
// Statistik: Verstorbene + Abgegebene für Verluste/Bestandskurve
|
||||
{ ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' },
|
||||
{ ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' },
|
||||
{ ...gerbil('pauli', 'Pauli', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-huber' },
|
||||
{ ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' },
|
||||
{ ...gerbil('pauli-breeder', 'Pauli Breeder', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-meier' },
|
||||
{ ...gerbil('pauli-neither', 'Pauli Neither', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-neither' },
|
||||
// ABGABE: zwei Tiere zur Abgabe im Quarantänebecken (enc-leer ist leer, kein Konflikt
|
||||
// mit dem Becken-Belegungstest der Großbecken-2-Tiere-Assertion).
|
||||
{
|
||||
@@ -189,9 +191,10 @@ export function seedDb(): MockDb {
|
||||
|
||||
// FEAT-13: contactInfo (Freitext) wurde durch strukturierte Felder ersetzt.
|
||||
const contacts: Contact[] = [
|
||||
{ id: 'con-meier', name: 'Zoohandlung Meier', email: 'meier@example.de', phone: null, address: 'Hauptstraße 1, 12345 Musterstadt', notes: null },
|
||||
{ id: 'con-huber', name: 'Familie Huber', email: null, phone: '0151 2345678', address: null, notes: null },
|
||||
{ id: 'con-frei', name: 'Züchterin Frei', email: null, phone: null, address: null, notes: 'unverknüpft' },
|
||||
{ id: 'con-meier', name: 'Zoohandlung Meier', email: 'meier@example.de', phone: null, address: 'Hauptstraße 1, 12345 Musterstadt', notes: null, isBreeder: true, isReceiver: true },
|
||||
{ id: 'con-huber', name: 'Familie Huber', email: null, phone: '0151 2345678', address: null, notes: null, isBreeder: false, isReceiver: true },
|
||||
{ id: 'con-frei', name: 'Züchterin Frei', email: null, phone: null, address: null, notes: 'unverknüpft', isBreeder: true, isReceiver: false },
|
||||
{ id: 'con-neither', name: 'Weder Noch', email: null, phone: null, address: null, notes: 'weder züchter noch abnehmer', isBreeder: false, isReceiver: false },
|
||||
]
|
||||
|
||||
const colorVarieties = [
|
||||
|
||||
@@ -46,7 +46,7 @@ test('Neues Tier anlegen mit Status Liebhaber', async ({ page }) => {
|
||||
const name = uniqueName('Tier')
|
||||
await page.goto('/rennmaeuse/neu')
|
||||
await page.getByLabel(`${t.fields.name} *`).fill(name)
|
||||
await page.getByLabel(`${t.fields.gender} *`).selectOption({ label: t.genderLabels.male })
|
||||
await page.getByRole('button', { name: t.genderLabels.male, exact: false }).click()
|
||||
const statusField = page.locator('label.field', {
|
||||
has: page.locator(`span:text-is("${t.fields.status}")`),
|
||||
})
|
||||
@@ -54,3 +54,41 @@ test('Neues Tier anlegen mit Status Liebhaber', async ({ page }) => {
|
||||
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Gehege-Feld Sichtbarkeit bei verstorbenen/abgegebenen Tieren', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
|
||||
// 1. Krümel ist Breeding, hat Gehege 'Großbecken'
|
||||
await page.goto('/rennmaeuse/kruemel')
|
||||
await expect(page.locator('dt', { hasText: t.fields.enclosure })).toBeVisible()
|
||||
await expect(page.locator('dd', { hasText: 'Großbecken' })).toBeVisible()
|
||||
|
||||
// 2. Willi ist Deceased (Verstorben) -> Gehege ausgeblendet
|
||||
await page.goto('/rennmaeuse/willi')
|
||||
await expect(page.locator('dt', { hasText: t.fields.enclosure })).toBeHidden()
|
||||
|
||||
// 3. Pauli ist GivenAway (Abgegeben), Abnehmer ist Familie Huber (isReceiver=true) -> Gehege eingeblendet
|
||||
await page.goto('/rennmaeuse/pauli')
|
||||
await expect(page.locator('dt', { hasText: t.fields.enclosure })).toBeVisible()
|
||||
|
||||
// 4. Pauli Breeder ist GivenAway, Abnehmer ist Zoohandlung Meier (isBreeder=true) -> Gehege eingeblendet
|
||||
await page.goto('/rennmaeuse/pauli-breeder')
|
||||
await expect(page.locator('dt', { hasText: t.fields.enclosure })).toBeVisible()
|
||||
|
||||
// 5. Pauli Neither ist GivenAway, Abnehmer ist Weder Noch (weder Züchter noch Abnehmer) -> Gehege ausgeblendet
|
||||
await page.goto('/rennmaeuse/pauli-neither')
|
||||
await expect(page.locator('dt', { hasText: t.fields.enclosure })).toBeHidden()
|
||||
})
|
||||
|
||||
test('Verstorbene Tiere: Abnehmer und Inserat-Button ausgeblendet', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
|
||||
// Willi ist verstorben -> "Abnehmer"-Zeile ausgeblendet und "Zur Abgabe stellen"-Button ausgeblendet
|
||||
await page.goto('/rennmaeuse/willi')
|
||||
await expect(page.locator('dt', { hasText: t.fields.receiver })).toBeHidden()
|
||||
await expect(page.getByRole('button', { name: de.pages.abgabe.markAction })).toBeHidden()
|
||||
|
||||
// Krümel lebt -> "Zur Abgabe stellen"-Button vorhanden
|
||||
await page.goto('/rennmaeuse/kruemel')
|
||||
await expect(page.getByRole('button', { name: de.pages.abgabe.markAction })).toBeVisible()
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ test('Neue Rennmaus anlegen — Validierung + Erfolg', async ({ page }) => {
|
||||
|
||||
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.genderLabels.female, exact: false }).click()
|
||||
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||
|
||||
// Erfolg: Detailseite des neuen Tieres
|
||||
@@ -174,7 +174,7 @@ test('Detailseite: Würfe als Elternteil zeigt verlinkten Wurf (ANIMAL-LITTERS)'
|
||||
// Fridolin ist fatherId von 'w-kruemel' (Wurf K)
|
||||
await page.goto('/rennmaeuse/fridolin')
|
||||
await expect(page.getByRole('heading', { name: 'Fridolin' })).toBeVisible()
|
||||
await expect(page.getByText(t.detail.parentLittersTitle)).toBeVisible()
|
||||
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
|
||||
// Wurf K als Link sichtbar
|
||||
const litterLink = page.getByRole('link', { name: /Wurf K/ })
|
||||
await expect(litterLink).toBeVisible()
|
||||
@@ -189,6 +189,6 @@ test('Detailseite: Keine Würfe als Elternteil zeigt Leer-Zustand (ANIMAL-LITTER
|
||||
skipUnlessMock()
|
||||
// Krümel hat keine Würfe als Elternteil (nur als Kind von w-kruemel)
|
||||
await page.goto('/rennmaeuse/kruemel')
|
||||
await expect(page.getByText(t.detail.parentLittersTitle)).toBeVisible()
|
||||
await expect(page.getByRole('heading', { name: t.detail.parentLittersTitle })).toBeVisible()
|
||||
await expect(page.getByText(t.detail.parentLittersEmpty)).toBeVisible()
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ test('Jungtier erfassen — Formular ist mit Wurf + Geburtsdatum vorbelegt', asy
|
||||
|
||||
const name = uniqueName('Jungtier')
|
||||
await page.getByLabel(`${tg.fields.name} *`).fill(name)
|
||||
await page.getByLabel(`${tg.fields.gender} *`).selectOption({ label: tg.genderLabels.male })
|
||||
await page.getByRole('button', { name: tg.genderLabels.male, exact: false }).click()
|
||||
await page.getByRole('button', { name: tg.form.save, exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user