import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import GroupsListPanel from './GroupsListPanel.svelte'; vi.mock('$app/state', () => ({ page: { url: { pathname: '/admin/groups/g1' } } })); afterEach(cleanup); const groups = [ { id: 'g1', name: 'Administrators', permissions: ['ADMIN', 'WRITE_ALL'] }, { id: 'g2', name: 'Editors', permissions: ['WRITE_ALL'] }, { id: 'g3', name: 'Readers', permissions: [] } ]; describe('GroupsListPanel — header', () => { it('renders the panel title', async () => { render(GroupsListPanel, { groups }); await expect.element(page.getByText(/Alle Gruppen/i)).toBeInTheDocument(); }); it('renders a new-group link pointing to /admin/groups/new', async () => { render(GroupsListPanel, { groups }); await expect .element(page.getByRole('link', { name: /neue gruppe/i })) .toHaveAttribute('href', '/admin/groups/new'); }); }); describe('GroupsListPanel — group items', () => { it('renders each group name', async () => { render(GroupsListPanel, { groups }); await expect.element(page.getByRole('link', { name: /administrators/i })).toBeInTheDocument(); await expect.element(page.getByRole('link', { name: /editors/i })).toBeInTheDocument(); }); it('each group links to /admin/groups/[id]', async () => { const { container } = render(GroupsListPanel, { groups }); const links = container.querySelectorAll('a[href^="/admin/groups/g"]'); expect(links.length).toBe(3); expect(links[0].getAttribute('href')).toBe('/admin/groups/g1'); }); it('shows permission count as subtitle', async () => { render(GroupsListPanel, { groups }); // Administrators has 2 permissions await expect.element(page.getByText(/2 Berechtigungen/i)).toBeInTheDocument(); }); it('shows "no permissions" for a group with zero permissions', async () => { render(GroupsListPanel, { groups }); await expect.element(page.getByText(/0 Berechtigungen/i)).toBeInTheDocument(); }); }); describe('GroupsListPanel — active state', () => { it('marks the active group link with aria-current=page', async () => { render(GroupsListPanel, { groups }); await expect .element(page.getByRole('link', { name: /administrators/i })) .toHaveAttribute('aria-current', 'page'); }); it('does not mark inactive group links with aria-current', async () => { render(GroupsListPanel, { groups }); await expect .element(page.getByRole('link', { name: /editors/i })) .not.toHaveAttribute('aria-current'); }); }); describe('GroupsListPanel — empty state', () => { it('shows empty state when groups array is empty', async () => { render(GroupsListPanel, { groups: [] }); await expect.element(page.getByText(/keine gruppen/i)).toBeInTheDocument(); }); }); // ─── Collapse toggle ────────────────────────────────────────────────────────── describe('GroupsListPanel — collapse toggle', () => { beforeEach(() => localStorage.removeItem('admin_groups_list_collapsed')); it('renders a collapse button with aria-label', async () => { render(GroupsListPanel, { groups }); await expect .element(page.getByRole('button', { name: /Liste einklappen/i })) .toBeInTheDocument(); }); it('clicking collapse shows the expand handle', async () => { render(GroupsListPanel, { groups }); await expect .element(page.getByRole('button', { name: /Liste einklappen/i })) .toBeInTheDocument(); document.querySelector('[aria-label="Liste einklappen"]')!.click(); await expect .element(page.getByRole('button', { name: /Liste ausklappen/i })) .toBeInTheDocument(); }); it('autocollapse prop starts the panel in collapsed state', async () => { render(GroupsListPanel, { groups, autocollapse: true }); await expect .element(page.getByRole('button', { name: /Liste ausklappen/i })) .toBeInTheDocument(); }); it('persists collapse state using the groups-specific localStorage key', async () => { render(GroupsListPanel, { groups }); const setSpy = vi.spyOn(Storage.prototype, 'setItem'); document.querySelector('[aria-label="Liste einklappen"]')!.click(); await vi.waitFor(() => expect(setSpy).toHaveBeenCalledWith('admin_groups_list_collapsed', 'true') ); setSpy.mockRestore(); }); });