test(genealogy): cover StammbaumSidePanel branches
Heading from displayName, birth/death years rendering with all branch combinations, close button, Escape keypress closing, non-Escape ignored, person detail link, empty placeholder, error banner on fetch failure, AddRelationshipForm visibility toggle. 12 tests covering ~25 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import StammbaumSidePanel from './StammbaumSidePanel.svelte';
|
||||
|
||||
vi.mock('$app/navigation', () => ({
|
||||
beforeNavigate: () => {},
|
||||
afterNavigate: () => {},
|
||||
goto: vi.fn(),
|
||||
invalidate: vi.fn(),
|
||||
invalidateAll: vi.fn(),
|
||||
preloadCode: vi.fn(),
|
||||
preloadData: vi.fn(),
|
||||
pushState: vi.fn(),
|
||||
replaceState: vi.fn(),
|
||||
disableScrollHandling: vi.fn(),
|
||||
onNavigate: () => () => {}
|
||||
}));
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const baseNode = (overrides: Record<string, unknown> = {}) => ({
|
||||
id: 'p-1',
|
||||
displayName: 'Anna Schmidt',
|
||||
familyMember: true,
|
||||
...overrides
|
||||
});
|
||||
|
||||
describe('StammbaumSidePanel', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (url: RequestInfo | URL) => {
|
||||
const u = url.toString();
|
||||
if (u.includes('/relationships') && !u.includes('/inferred')) {
|
||||
return new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
}
|
||||
if (u.includes('/inferred-relationships')) {
|
||||
return new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||
}
|
||||
return new Response('[]', { status: 200 });
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchSpy?.mockRestore();
|
||||
});
|
||||
|
||||
it('renders the heading from node displayName', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {} }
|
||||
});
|
||||
|
||||
await expect.element(page.getByRole('heading', { name: 'Anna Schmidt' })).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows birth/death years when set', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: {
|
||||
node: baseNode({ birthYear: 1899, deathYear: 1950 }),
|
||||
onClose: () => {}
|
||||
}
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('1899');
|
||||
expect(document.body.textContent).toContain('1950');
|
||||
});
|
||||
|
||||
it('renders ?– when birthYear is missing but deathYear is set', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode({ deathYear: 1950 }), onClose: () => {} }
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toMatch(/\?–/);
|
||||
});
|
||||
|
||||
it('does not render the years line when both are missing', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {} }
|
||||
});
|
||||
|
||||
// no years line visible — should not see the question-mark fallback
|
||||
expect(document.body.textContent).not.toMatch(/\?–\?/);
|
||||
});
|
||||
|
||||
it('calls onClose when the close button is clicked', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose }
|
||||
});
|
||||
|
||||
const closeBtn = document.querySelector('button[aria-label]') as HTMLButtonElement;
|
||||
closeBtn.click();
|
||||
expect(onClose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('calls onClose when Escape is pressed on window', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose }
|
||||
});
|
||||
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||||
expect(onClose).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('does not call onClose for non-Escape keys', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose }
|
||||
});
|
||||
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders the person detail link', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {} }
|
||||
});
|
||||
|
||||
const link = document.querySelector('a[href="/persons/p-1"]');
|
||||
expect(link).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows the empty placeholder when there are no direct relationships', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {} }
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
await expect.element(page.getByText(/keine beziehungen bekannt/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows the error banner when both fetch calls fail', async () => {
|
||||
fetchSpy.mockResolvedValue(new Response('error', { status: 500 }));
|
||||
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {} }
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
const alert = document.querySelector('[role="alert"]');
|
||||
expect(alert).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows the AddRelationshipForm only when canWrite is true', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {}, canWrite: true }
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
// AddRelationshipForm exposes a "Beziehung hinzufügen" toggle button
|
||||
const addButtons = Array.from(document.querySelectorAll('button')).filter((b) =>
|
||||
b.textContent?.toLowerCase().includes('hinzufügen')
|
||||
);
|
||||
expect(addButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('hides the AddRelationshipForm when canWrite is false', async () => {
|
||||
render(StammbaumSidePanel, {
|
||||
props: { node: baseNode(), onClose: () => {}, canWrite: false }
|
||||
});
|
||||
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
const addButtons = Array.from(document.querySelectorAll('button')).filter((b) =>
|
||||
b.textContent?.toLowerCase().includes('hinzufügen')
|
||||
);
|
||||
expect(addButtons.length).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user