- Kontakt-Detailseite: „Fehler melden"- und „Datenherkunft"-Button. - Wurf-Ansicht: „Datenherkunft"-Button (Feedback war bereits vorhanden). - Feedback-Entity um loses, nullable ContactId erweitert (kein FK → übersteht Ingest-Wipe); Migration AddFeedbackContactId. - Contact.Provenance + Litter.Provenance (nullable text); Migration AddContactLitterProvenance; im Ingest gemappt und in den DTOs zurückgegeben. - Import: build_entity_provenance() generalisiert; Kontakte (sourceFiles, Züchter/Abnehmer-Hinweise) und Würfe (Wurfchronik vs. Diagramm-rekonstruiert, Geschwister-Merge) erhalten Herkunftsdaten in resolved_import.json. - Frontend: ProvenanceDialog generalisiert (EntityProvenance + entityLabel). Tests erweitert (Ingest-Round-trip Kontakt/Wurf, contact-scoped Feedback übersteht Wipe). dotnet(212)/vitest(129)/playwright(36)/tsc/eslint grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
/** FEEDBACK: "Fehler melden" Dialog + Stammbaum-Rechtsklick ("ID kopieren" / "Fehler melden"). */
|
|
import { de, expect, skipUnlessMock, test } from './fixtures'
|
|
|
|
const f = de.feedback
|
|
const st = de.pages.stammbaum
|
|
|
|
test('Tierakte: "Fehler melden"-Button öffnet den Dialog, Senden zeigt Erfolg', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/rennmaeuse/kruemel')
|
|
|
|
// Button in den Aktionen öffnet den Dialog.
|
|
await page.getByRole('button', { name: f.button }).click()
|
|
const dialog = page.getByRole('dialog', { name: f.dialogTitle })
|
|
await expect(dialog).toBeVisible()
|
|
|
|
// Debug-Kontext ist sichtbar (automatisch erfasst).
|
|
await expect(dialog).toContainText(f.debugTitle)
|
|
await expect(dialog).toContainText(f.contexts['gerbil-detail'])
|
|
|
|
// Beschreibung eintragen + senden.
|
|
await dialog.getByLabel(f.label).fill('Der Farbschlag stimmt nicht.')
|
|
await dialog.getByRole('button', { name: f.submit }).click()
|
|
|
|
// Erfolgs-Toast + Dialog schließt.
|
|
await expect(page.getByText(f.success)).toBeVisible()
|
|
await expect(dialog).not.toBeVisible()
|
|
|
|
// Der Bericht wurde mit Debug-Kontext gespeichert.
|
|
expect(mockDb).not.toBeNull()
|
|
const reports = mockDb!.feedback
|
|
expect(reports.length).toBe(1)
|
|
expect(reports[0]).toMatchObject({
|
|
message: 'Der Farbschlag stimmt nicht.',
|
|
context: 'gerbil-detail',
|
|
gerbilId: 'kruemel',
|
|
})
|
|
expect(reports[0].url).toContain('/rennmaeuse/kruemel')
|
|
expect(typeof reports[0].clientTimestamp).toBe('string')
|
|
})
|
|
|
|
test('Wurf-Ansicht: "Fehler melden"-Button öffnet den Dialog und sendet mit Wurf-Kontext', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/wuerfe/w-kruemel')
|
|
|
|
await page.getByRole('button', { name: f.button }).click()
|
|
const dialog = page.getByRole('dialog', { name: f.dialogTitle })
|
|
await expect(dialog).toBeVisible()
|
|
await expect(dialog).toContainText(f.contexts['litter-detail'])
|
|
|
|
await dialog.getByLabel(f.label).fill('Die Wurfstärke ist falsch.')
|
|
await dialog.getByRole('button', { name: f.submit }).click()
|
|
await expect(page.getByText(f.success)).toBeVisible()
|
|
|
|
const reports = mockDb!.feedback
|
|
expect(reports.length).toBe(1)
|
|
expect(reports[0]).toMatchObject({ context: 'litter-detail', litterId: 'w-kruemel' })
|
|
})
|
|
|
|
test('Kontakt-Detail: "Fehler melden"-Button sendet mit Kontakt-Kontext', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/kontakte/con-meier')
|
|
|
|
await page.getByRole('button', { name: f.button }).click()
|
|
const dialog = page.getByRole('dialog', { name: f.dialogTitle })
|
|
await expect(dialog).toBeVisible()
|
|
await expect(dialog).toContainText(f.contexts['contact-detail'])
|
|
|
|
await dialog.getByLabel(f.label).fill('Die Adresse stimmt nicht.')
|
|
await dialog.getByRole('button', { name: f.submit }).click()
|
|
await expect(page.getByText(f.success)).toBeVisible()
|
|
|
|
const reports = mockDb!.feedback
|
|
expect(reports.length).toBe(1)
|
|
expect(reports[0]).toMatchObject({ context: 'contact-detail', contactId: 'con-meier' })
|
|
})
|
|
|
|
test('Stammbaum: Rechtsklick auf eine Karte zeigt "ID kopieren" + "Fehler melden"', async ({ page, mockDb }) => {
|
|
skipUnlessMock()
|
|
await page.goto('/rennmaeuse/kruemel/stammbaum')
|
|
await expect(page.locator('.pedigree-card').first()).toBeVisible()
|
|
|
|
// Rechtsklick auf die Wurzelkarte (Krümel).
|
|
await page.locator('.pedigree-card--root').click({ button: 'right' })
|
|
|
|
const menu = page.locator('.stammbaum-context-menu')
|
|
await expect(menu).toBeVisible()
|
|
await expect(menu.getByRole('menuitem', { name: st.contextMenu.copyId })).toBeVisible()
|
|
await expect(menu.getByRole('menuitem', { name: st.contextMenu.reportError })).toBeVisible()
|
|
|
|
// "Fehler melden" öffnet den Dialog mit Stammbaum-Kontext.
|
|
await menu.getByRole('menuitem', { name: st.contextMenu.reportError }).click()
|
|
const dialog = page.getByRole('dialog', { name: f.dialogTitle })
|
|
await expect(dialog).toBeVisible()
|
|
await expect(dialog).toContainText(f.contexts.stammbaum)
|
|
|
|
await dialog.getByLabel(f.label).fill('Stammbaum-Bug.')
|
|
await dialog.getByRole('button', { name: f.submit }).click()
|
|
await expect(page.getByText(f.success)).toBeVisible()
|
|
|
|
const reports = mockDb!.feedback
|
|
expect(reports.length).toBe(1)
|
|
expect(reports[0]).toMatchObject({ context: 'stammbaum', gerbilId: 'kruemel' })
|
|
})
|