-
{node.displayName}
- {#if node.birthYear || node.deathYear}
-
- {node.birthYear ?? '?'}–{node.deathYear ?? ''}
-
- {/if}
+
+
+
{node.displayName}
+ {#if node.birthYear || node.deathYear}
+
+ {node.birthYear ?? '?'}–{node.deathYear ?? ''}
+
+ {/if}
+
+
{#if error}
diff --git a/frontend/src/lib/components/StammbaumSidePanel.svelte.spec.ts b/frontend/src/lib/components/StammbaumSidePanel.svelte.spec.ts
new file mode 100644
index 00000000..f924bbd4
--- /dev/null
+++ b/frontend/src/lib/components/StammbaumSidePanel.svelte.spec.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import StammbaumSidePanel from './StammbaumSidePanel.svelte';
+
+vi.mock('$app/navigation', () => ({ invalidateAll: vi.fn() }));
+vi.mock('$lib/components/PersonTypeahead.svelte', () => ({ default: () => null }));
+
+const makeNode = () => ({
+ id: 'person-1',
+ displayName: 'Alice Müller',
+ birthYear: 1900,
+ deathYear: null,
+ familyMember: true
+});
+
+function stubFetch(directRels: unknown[] = [], inferredRels: unknown[] = []) {
+ let callCount = 0;
+ vi.stubGlobal(
+ 'fetch',
+ vi.fn().mockImplementation(() => {
+ callCount++;
+ const body = callCount % 2 === 1 ? directRels : inferredRels;
+ return Promise.resolve({ ok: true, json: () => Promise.resolve(body) });
+ })
+ );
+}
+
+beforeEach(() => stubFetch());
+afterEach(() => {
+ cleanup();
+ vi.unstubAllGlobals();
+});
+
+describe('StammbaumSidePanel', () => {
+ it('calls onClose when dismiss button is clicked', async () => {
+ const onClose = vi.fn();
+ render(StammbaumSidePanel, { node: makeNode(), onClose, canWrite: false });
+ await expect.element(page.getByRole('button', { name: 'Schließen' })).toBeInTheDocument();
+ const btn = [...document.querySelectorAll
('button')].find(
+ (b) => b.getAttribute('aria-label') === 'Schließen'
+ );
+ btn!.click();
+ expect(onClose).toHaveBeenCalledOnce();
+ });
+});