test: add e2e tests

This commit is contained in:
Marcel
2026-03-17 13:33:33 +00:00
parent 973620a097
commit 7cb20dec50
20 changed files with 3272 additions and 1376 deletions

View File

@@ -0,0 +1,184 @@
import { describe, expect, it, vi, afterEach } from 'vitest';
import { render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import PersonMultiSelect from './PersonMultiSelect.svelte';
const waitForDebounce = () => new Promise((r) => setTimeout(r, 350));
const tick = () => new Promise((r) => setTimeout(r, 0));
const PERSONS = [
{ id: '1', firstName: 'Max', lastName: 'Mustermann' },
{ id: '2', firstName: 'Anna', lastName: 'Musterfrau' },
{ id: '3', firstName: 'Karl', lastName: 'König' }
];
function mockFetch(persons = PERSONS) {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue(persons)
})
);
}
function receiverInputs() {
return Array.from(
document.querySelectorAll<HTMLInputElement>('input[type="hidden"][name="receiverIds"]')
);
}
afterEach(() => {
vi.unstubAllGlobals();
});
// ─── Rendering ────────────────────────────────────────────────────────────────
describe('PersonMultiSelect rendering', () => {
it('renders the text input with placeholder when no persons selected', async () => {
render(PersonMultiSelect, { selectedPersons: [] });
await expect.element(page.getByPlaceholder('Namen tippen...')).toBeInTheDocument();
await page.screenshot({ path: 'test-results/screenshots/person-multiselect-empty.png' });
});
it('renders pre-selected persons as chips', async () => {
render(PersonMultiSelect, {
selectedPersons: [
{ id: '1', firstName: 'Max', lastName: 'Mustermann' },
{ id: '2', firstName: 'Anna', lastName: 'Musterfrau' }
]
});
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
await expect.element(page.getByText('Anna Musterfrau')).toBeInTheDocument();
await page.screenshot({ path: 'test-results/screenshots/person-multiselect-with-chips.png' });
});
it('renders hidden inputs for each selected person', async () => {
render(PersonMultiSelect, {
selectedPersons: [
{ id: '1', firstName: 'Max', lastName: 'Mustermann' },
{ id: '2', firstName: 'Anna', lastName: 'Musterfrau' }
]
});
await tick();
const inputs = receiverInputs();
expect(inputs).toHaveLength(2);
expect(inputs[0].value).toBe('1');
expect(inputs[1].value).toBe('2');
});
it('hides the placeholder when persons are selected', async () => {
render(PersonMultiSelect, {
selectedPersons: [{ id: '1', firstName: 'Max', lastName: 'Mustermann' }]
});
await expect.element(page.getByPlaceholder('Namen tippen...')).not.toBeInTheDocument();
});
});
// ─── Selecting persons ────────────────────────────────────────────────────────
describe('PersonMultiSelect selecting persons', () => {
it('adds a person chip on result click', async () => {
mockFetch();
render(PersonMultiSelect, { selectedPersons: [] });
const input = page.getByRole('textbox');
await input.fill('Mu');
await waitForDebounce();
await page.getByText('Mustermann, Max').click();
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
await expect.element(input).toHaveValue('');
await page.screenshot({
path: 'test-results/screenshots/person-multiselect-one-selected.png'
});
});
it('can select multiple persons sequentially', async () => {
mockFetch();
render(PersonMultiSelect, { selectedPersons: [] });
const input = page.getByRole('textbox');
await input.fill('Mu');
await waitForDebounce();
await page.getByText('Mustermann, Max').click();
await input.fill('Mu');
await waitForDebounce();
await page.getByText('Musterfrau, Anna').click();
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
await expect.element(page.getByText('Anna Musterfrau')).toBeInTheDocument();
await page.screenshot({
path: 'test-results/screenshots/person-multiselect-two-selected.png'
});
});
it('filters already-selected persons from search results', async () => {
mockFetch();
render(PersonMultiSelect, {
selectedPersons: [{ id: '1', firstName: 'Max', lastName: 'Mustermann' }]
});
const input = page.getByRole('textbox');
await input.fill('Mu');
await waitForDebounce();
await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument();
await expect.element(page.getByText('Musterfrau, Anna')).toBeInTheDocument();
});
it('selects a result with Enter key', async () => {
mockFetch([{ id: '1', firstName: 'Max', lastName: 'Mustermann' }]);
render(PersonMultiSelect, { selectedPersons: [] });
const input = page.getByRole('textbox');
await input.fill('Ma');
await waitForDebounce();
await page.getByText('Mustermann, Max').click();
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
});
});
// ─── Removing persons ─────────────────────────────────────────────────────────
describe('PersonMultiSelect removing persons', () => {
it('removes a chip when its × button is clicked', async () => {
render(PersonMultiSelect, {
selectedPersons: [
{ id: '1', firstName: 'Max', lastName: 'Mustermann' },
{ id: '2', firstName: 'Anna', lastName: 'Musterfrau' }
]
});
// Buttons have aria-label="Entfernen"
const removeButtons = page.getByRole('button', { name: 'Entfernen' });
await removeButtons.first().click();
await expect.element(page.getByText('Max Mustermann')).not.toBeInTheDocument();
await expect.element(page.getByText('Anna Musterfrau')).toBeInTheDocument();
});
it('removes the corresponding hidden input when a chip is removed', async () => {
render(PersonMultiSelect, {
selectedPersons: [
{ id: '1', firstName: 'Max', lastName: 'Mustermann' },
{ id: '2', firstName: 'Anna', lastName: 'Musterfrau' }
]
});
await page.getByRole('button', { name: 'Entfernen' }).first().click();
await tick();
const inputs = receiverInputs();
expect(inputs).toHaveLength(1);
expect(inputs[0].value).toBe('2');
});
});
// ─── Click outside ────────────────────────────────────────────────────────────
describe('PersonMultiSelect click outside', () => {
it('closes the dropdown when clicking outside', async () => {
mockFetch();
render(PersonMultiSelect, { selectedPersons: [] });
const input = page.getByRole('textbox');
await input.fill('Mu');
await waitForDebounce();
await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument();
document.body.click();
await tick();
await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument();
});
});