diff --git a/frontend/src/routes/admin/users/UsersListPanel.svelte.test.ts b/frontend/src/routes/admin/users/UsersListPanel.svelte.test.ts new file mode 100644 index 00000000..83281d6b --- /dev/null +++ b/frontend/src/routes/admin/users/UsersListPanel.svelte.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page as browserPage } from 'vitest/browser'; + +const mockPage = { url: new URL('http://localhost/admin/users') }; + +vi.mock('$app/state', () => ({ + get page() { + return mockPage; + } +})); + +beforeEach(() => { + localStorage.clear(); +}); + +afterEach(cleanup); + +async function loadComponent() { + return (await import('./UsersListPanel.svelte')).default; +} + +const baseUsers = [ + { + id: 'u1', + email: 'anna@example.com', + firstName: 'Anna', + lastName: 'Schmidt', + groups: [{ id: 'g1', name: 'Familie', permissions: [] }] + }, + { + id: 'u2', + email: 'no-name@example.com', + firstName: null, + lastName: null, + groups: [] + } +]; + +describe('UsersListPanel', () => { + it('renders the expanded list with header and search input', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + await expect.element(browserPage.getByText('Alle Benutzer')).toBeVisible(); + await expect.element(browserPage.getByPlaceholder(/benutzer suchen/i)).toBeVisible(); + }); + + it('renders one row per user with email + fullName when both names are set', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + await expect.element(browserPage.getByText('anna@example.com')).toBeVisible(); + await expect.element(browserPage.getByText('Anna Schmidt')).toBeVisible(); + }); + + it('omits the fullName line when both first and last names are null', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + await expect.element(browserPage.getByText('no-name@example.com')).toBeVisible(); + }); + + it('renders group chips when the user has groups', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + await expect.element(browserPage.getByText('Familie')).toBeVisible(); + }); + + it('filters the list by the search query', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + const search = (await browserPage + .getByPlaceholder(/benutzer suchen/i) + .element()) as HTMLInputElement; + search.value = 'Anna'; + search.dispatchEvent(new Event('input', { bubbles: true })); + + await expect.element(browserPage.getByText('anna@example.com')).toBeVisible(); + await expect.element(browserPage.getByText('no-name@example.com')).not.toBeInTheDocument(); + }); + + it('renders the empty placeholder when filter matches nothing', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + const search = (await browserPage + .getByPlaceholder(/benutzer suchen/i) + .element()) as HTMLInputElement; + search.value = 'NoMatch12345'; + search.dispatchEvent(new Event('input', { bubbles: true })); + + await expect.element(browserPage.getByText('Keine Benutzer vorhanden.')).toBeVisible(); + }); + + it('marks the active user with aria-current=page', async () => { + mockPage.url = new URL('http://localhost/admin/users/u2'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers } }); + + const links = Array.from( + document.querySelectorAll('a[href^="/admin/users/"]') + ) as HTMLAnchorElement[]; + const u2 = links.find((a) => a.href.endsWith('/admin/users/u2')); + expect(u2?.getAttribute('aria-current')).toBe('page'); + }); + + it('renders collapsed view when autocollapse is true', async () => { + mockPage.url = new URL('http://localhost/admin/users'); + const Panel = await loadComponent(); + render(Panel, { props: { users: baseUsers, autocollapse: true } }); + + await expect + .element(browserPage.getByRole('button', { name: /liste ausklappen/i })) + .toBeVisible(); + }); +});