test(admin/users): cover UsersListPanel branches
Expanded list, per-user email+fullName rendering with null-name fallback, group chip rendering, search filter (positive + empty result branches), aria-current matrix, collapsed view via autocollapse. 8 tests, ~25 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
125
frontend/src/routes/admin/users/UsersListPanel.svelte.test.ts
Normal file
125
frontend/src/routes/admin/users/UsersListPanel.svelte.test.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user