Das Rechtsklick-Kontextmenü (ID kopieren / Fehler melden) war auf Mobil nicht erreichbar (onContextMenu feuert auf Touch nicht zuverlässig). Jetzt öffnet ein langer Druck (~500 ms ohne Verschieben) auf eine Ahnenkarte dasselbe Menü; der darauf folgende synthetische Klick (Umwurzeln) wird unterdrückt. Bewegung >10 px bricht den Long-Press ab (kollidiert nicht mit Pan/Zoom). Legenden-Hinweis ergänzt. e2e: Long-Press öffnet das Menü (desktop; phone-Projekt übersprungen, da synthetischer Touch dort instabil). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
133 lines
5.7 KiB
TypeScript
133 lines
5.7 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' })
|
|
})
|
|
|
|
test('Stammbaum: langer Druck (Long-Press) öffnet dasselbe Kontextmenü', async ({ page }, testInfo) => {
|
|
skipUnlessMock()
|
|
// Der Long-Press-Code-Pfad (onTouchStart → Timer → Menü) ist projekt-unabhängig
|
|
// identisch; im Touch-emulierten phone-Projekt ist das Auslösen eines
|
|
// synthetischen TouchEvents unzuverlässig, daher hier auf das desktop-Projekt
|
|
// beschränkt (validiert dieselbe Logik).
|
|
testInfo.skip(testInfo.project.name === 'phone', 'synthetischer Long-Press im phone-Projekt instabil')
|
|
await page.goto('/rennmaeuse/kruemel/stammbaum')
|
|
const card = page.locator('.pedigree-card--root')
|
|
await expect(card).toBeVisible()
|
|
|
|
// Long-Press simulieren: echtes touchstart (mit Touch-Objekt) auslösen; der
|
|
// Timer im PedigreeCard öffnet nach ~500 ms das Menü.
|
|
const dispatched = await card.evaluate((el) => {
|
|
if (typeof Touch === 'undefined' || typeof TouchEvent === 'undefined') return false
|
|
const r = el.getBoundingClientRect()
|
|
const t = new Touch({ identifier: 1, target: el, clientX: r.left + 10, clientY: r.top + 10 })
|
|
el.dispatchEvent(
|
|
new TouchEvent('touchstart', { bubbles: true, cancelable: true, touches: [t], targetTouches: [t], changedTouches: [t] }),
|
|
)
|
|
return true
|
|
})
|
|
test.skip(!dispatched, 'TouchEvent in diesem Projekt nicht verfügbar')
|
|
await expect(page.locator('.stammbaum-context-menu')).toBeVisible({ timeout: 2000 })
|
|
await expect(
|
|
page.locator('.stammbaum-context-menu').getByRole('menuitem', { name: st.contextMenu.copyId }),
|
|
).toBeVisible()
|
|
})
|