import { describe, it, expect, vi, afterEach } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import AddRelationshipForm from './AddRelationshipForm.svelte'; afterEach(cleanup); describe('AddRelationshipForm', () => { it('renders the toggle button to open the form', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await expect.element(page.getByRole('button', { name: /hinzufügen/i })).toBeVisible(); }); it('opens the form when the toggle button is clicked', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); expect(document.querySelector('select[name="relationType"]')).not.toBeNull(); }); it('renders all relationship type options when open', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); const select = document.querySelector('select[name="relationType"]') as HTMLSelectElement; const optionValues = Array.from(select.options).map((o) => o.value); expect(optionValues).toContain('PARENT_OF'); expect(optionValues).toContain('SPOUSE_OF'); expect(optionValues).toContain('FRIEND'); expect(optionValues).toContain('OTHER'); }); it('shows the year-error alert when toYear is before fromYear', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); const fromInput = document.querySelector('input[name="fromYear"]') as HTMLInputElement; const toInput = document.querySelector('input[name="toYear"]') as HTMLInputElement; fromInput.value = '1923'; fromInput.dispatchEvent(new Event('input', { bubbles: true })); toInput.value = '1920'; toInput.dispatchEvent(new Event('input', { bubbles: true })); await expect.element(page.getByText(/bis-jahr darf nicht vor von-jahr/i)).toBeVisible(); }); it('does not show the year-error when toYear equals fromYear', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); const fromInput = document.querySelector('input[name="fromYear"]') as HTMLInputElement; const toInput = document.querySelector('input[name="toYear"]') as HTMLInputElement; fromInput.value = '1923'; fromInput.dispatchEvent(new Event('input', { bubbles: true })); toInput.value = '1923'; toInput.dispatchEvent(new Event('input', { bubbles: true })); await expect.element(page.getByText(/bis-jahr darf nicht/i)).not.toBeInTheDocument(); }); it('cancel button closes the form', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); expect(document.querySelector('select[name="relationType"]')).not.toBeNull(); const cancelBtn = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.toLowerCase().includes('abbrechen') ); expect(cancelBtn).toBeDefined(); cancelBtn?.click(); await vi.waitFor(() => { expect(document.querySelector('select[name="relationType"]')).toBeNull(); }); }); it('does not invoke onSubmit before user submission', async () => { const onSubmit = vi.fn().mockResolvedValue(undefined); render(AddRelationshipForm, { props: { personId: 'p-1', onSubmit } }); // Without a person selected, the form cannot be submitted by the user. expect(onSubmit).not.toHaveBeenCalled(); }); it('renders with use:enhance form action when onSubmit is undefined', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); await vi.waitFor(() => { expect(document.querySelector('form[action="?/addRelationship"]')).not.toBeNull(); }); }); it('renders the callback-based form when onSubmit is provided', async () => { render(AddRelationshipForm, { props: { personId: 'p-1', onSubmit: async () => {} } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); await vi.waitFor(() => { // callback form has no action attribute (just onsubmit handler) expect(document.querySelector('form[action="?/addRelationship"]')).toBeNull(); expect(document.querySelector('form')).not.toBeNull(); }); }); it('shows the self-error when the related person id equals personId', async () => { render(AddRelationshipForm, { props: { personId: 'p-self' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); const relInput = (await vi.waitFor(() => { const el = document.querySelector('input[name="relatedPersonId"]') as HTMLInputElement; expect(el).not.toBeNull(); return el; })) as HTMLInputElement; relInput.value = 'p-self'; relInput.dispatchEvent(new Event('input', { bubbles: true })); await expect.element(page.getByText(/selbst|self/i)).toBeVisible(); }); it('keeps submit disabled when no related person is selected', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); await vi.waitFor(() => { const submitBtn = document.querySelector('button[type="submit"]') as HTMLButtonElement | null; expect(submitBtn).not.toBeNull(); expect(submitBtn!.disabled).toBe(true); }); }); it('keeps submit disabled when there is a yearError', async () => { render(AddRelationshipForm, { props: { personId: 'p-1' } }); await page.getByRole('button', { name: /hinzufügen/i }).click(); const fromInput = document.querySelector('input[name="fromYear"]') as HTMLInputElement; const toInput = document.querySelector('input[name="toYear"]') as HTMLInputElement; const relInput = document.querySelector('input[name="relatedPersonId"]') as HTMLInputElement; fromInput.value = '1923'; fromInput.dispatchEvent(new Event('input', { bubbles: true })); toInput.value = '1920'; toInput.dispatchEvent(new Event('input', { bubbles: true })); relInput.value = 'p-other'; relInput.dispatchEvent(new Event('input', { bubbles: true })); await vi.waitFor(() => { const submitBtn = document.querySelector('button[type="submit"]') as HTMLButtonElement; expect(submitBtn.disabled).toBe(true); }); }); });