From f162c9b55be8ad86b40cc3d28a46ddb7422c6668 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 21:49:38 +0200 Subject: [PATCH] feat(persons): show merge panel inline on edit page, remove Gefahrenzone accordion Closes #342. The PersonDangerZone collapsible wrapper is removed; PersonMergePanel is now rendered directly in the edit page with its own red border (border-red-200), preserving the {#key person.id} state-reset behaviour and the two-step merge flow. Fix PersonTypeahead mock to use Svelte 5 functional stub (not Svelte 3/4 $$ internals). Co-Authored-By: Claude Sonnet 4.6 --- .../persons/[id]/PersonMergePanel.svelte | 2 +- .../[id]/PersonMergePanel.svelte.spec.ts | 55 +++++++++++++++++++ .../src/routes/persons/[id]/edit/+page.svelte | 6 +- .../persons/[id]/edit/PersonDangerZone.svelte | 44 --------------- 4 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 frontend/src/routes/persons/[id]/PersonMergePanel.svelte.spec.ts delete mode 100644 frontend/src/routes/persons/[id]/edit/PersonDangerZone.svelte diff --git a/frontend/src/routes/persons/[id]/PersonMergePanel.svelte b/frontend/src/routes/persons/[id]/PersonMergePanel.svelte index 92af8bb9..27447b38 100644 --- a/frontend/src/routes/persons/[id]/PersonMergePanel.svelte +++ b/frontend/src/routes/persons/[id]/PersonMergePanel.svelte @@ -15,7 +15,7 @@ let mergeTargetId = $state(''); let showMergeConfirm = $state(false); -
+

{m.person_merge_heading()}

diff --git a/frontend/src/routes/persons/[id]/PersonMergePanel.svelte.spec.ts b/frontend/src/routes/persons/[id]/PersonMergePanel.svelte.spec.ts new file mode 100644 index 00000000..4cfe9898 --- /dev/null +++ b/frontend/src/routes/persons/[id]/PersonMergePanel.svelte.spec.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import PersonMergePanel from './PersonMergePanel.svelte'; + +vi.mock('$app/forms', () => ({ enhance: () => () => {} })); +vi.mock('$lib/components/PersonTypeahead.svelte', () => ({ + default: () => null +})); + +afterEach(cleanup); + +const makePerson = (overrides = {}) => ({ + displayName: 'Hans Müller', + ...overrides +}); + +// ─── Danger indicator ──────────────────────────────────────────────────────── + +describe('PersonMergePanel — danger indicator', () => { + it('renders outer container with red border class', () => { + const { container } = render(PersonMergePanel, { + props: { person: makePerson(), form: null } + }); + const panel = container.firstElementChild as HTMLElement; + expect(panel?.classList.contains('border-red-200')).toBe(true); + }); +}); + +// ─── Initial state ──────────────────────────────────────────────────────────── + +describe('PersonMergePanel — initial state', () => { + it('renders merge heading', async () => { + render(PersonMergePanel, { props: { person: makePerson(), form: null } }); + const heading = page.getByRole('heading', { level: 2 }); + await expect.element(heading).toBeInTheDocument(); + }); + + it('merge button is disabled when no target selected', async () => { + render(PersonMergePanel, { props: { person: makePerson(), form: null } }); + const mergeBtn = page.getByRole('button', { name: /zusammenführen/i }); + await expect.element(mergeBtn).toBeDisabled(); + }); +}); + +// ─── Error state ────────────────────────────────────────────────────────────── + +describe('PersonMergePanel — error state', () => { + it('renders mergeError when form contains error', async () => { + render(PersonMergePanel, { + props: { person: makePerson(), form: { mergeError: 'Zielperson nicht gefunden.' } } + }); + await expect.element(page.getByText('Zielperson nicht gefunden.')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/routes/persons/[id]/edit/+page.svelte b/frontend/src/routes/persons/[id]/edit/+page.svelte index 43701e13..b49dc471 100644 --- a/frontend/src/routes/persons/[id]/edit/+page.svelte +++ b/frontend/src/routes/persons/[id]/edit/+page.svelte @@ -5,7 +5,7 @@ import BackButton from '$lib/components/BackButton.svelte'; import PersonEditForm from './PersonEditForm.svelte'; import PersonEditSaveBar from './PersonEditSaveBar.svelte'; import NameHistoryEditCard from './NameHistoryEditCard.svelte'; -import PersonDangerZone from './PersonDangerZone.svelte'; +import PersonMergePanel from '../PersonMergePanel.svelte'; let { data, form } = $props(); const person = $derived(data.person); @@ -35,7 +35,9 @@ const person = $derived(data.person); - + {#key person.id} + + {/key}

diff --git a/frontend/src/routes/persons/[id]/edit/PersonDangerZone.svelte b/frontend/src/routes/persons/[id]/edit/PersonDangerZone.svelte deleted file mode 100644 index 4161e1fc..00000000 --- a/frontend/src/routes/persons/[id]/edit/PersonDangerZone.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - -
- - - {#if open} -
- {#key person.id} - - {/key} -
- {/if} -
-- 2.49.1