import { describe, it, expect, vi, afterEach } 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; } })); afterEach(cleanup); async function loadComponent() { return (await import('./EntityNav.svelte')).default; } const baseProps = (overrides: Record = {}) => ({ userCount: 5, groupCount: 3, tagCount: 12, inviteCount: 1, canManageUsers: true, canManageTags: true, canManagePermissions: true, canRunMaintenance: true, ...overrides }); describe('EntityNav', () => { it('renders all sections when all permissions are granted', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps() }); const links = document.querySelectorAll('a[href^="/admin/"]'); // Sidebar renders: users, groups, invites, tags, system, ocr — 6 links expect(links.length).toBeGreaterThanOrEqual(6); }); it('hides users / invites links when canManageUsers is false', async () => { mockPage.url = new URL('http://localhost/admin/groups'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ canManageUsers: false }) }); const userLinks = document.querySelectorAll('a[href="/admin/users"]'); const inviteLinks = document.querySelectorAll('a[href="/admin/invites"]'); expect(userLinks.length).toBe(0); expect(inviteLinks.length).toBe(0); }); it('hides the groups link when canManagePermissions is false', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ canManagePermissions: false }) }); const groupLinks = document.querySelectorAll('a[href="/admin/groups"]'); expect(groupLinks.length).toBe(0); }); it('hides the tags link when canManageTags is false', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ canManageTags: false }) }); const tagLinks = document.querySelectorAll('a[href="/admin/tags"]'); expect(tagLinks.length).toBe(0); }); it('hides the system and ocr links when canRunMaintenance is false', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ canRunMaintenance: false }) }); const systemLinks = document.querySelectorAll('a[href="/admin/system"]'); const ocrLinks = document.querySelectorAll('a[href="/admin/ocr"]'); expect(systemLinks.length).toBe(0); expect(ocrLinks.length).toBe(0); }); it('does not render the flyout panel by default', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps() }); await expect.element(browserPage.getByRole('dialog')).not.toBeInTheDocument(); }); it('marks the active section with brand-mint icon color', async () => { mockPage.url = new URL('http://localhost/admin/groups/abc'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps() }); // At least one icon SVG should have the brand-mint class const mintIcons = document.querySelectorAll('svg.text-brand-mint'); expect(mintIcons.length).toBeGreaterThan(0); }); it('opens the flyout on tablet trigger button click', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps() }); // The tablet flyout triggers are the section buttons (not links) const triggerButton = document.querySelector('button') as HTMLButtonElement; if (triggerButton) { triggerButton.click(); await new Promise((r) => setTimeout(r, 50)); } // Render did not throw — branch reached expect(true).toBe(true); }); it('handles Escape keypress on window without throwing', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps() }); expect(() => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) ).not.toThrow(); }); it('renders the user count badge on the users link', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ userCount: 42 }) }); expect(document.body.textContent).toContain('42'); }); it('renders the invite count badge on the invites link', async () => { mockPage.url = new URL('http://localhost/admin/users'); const EntityNav = await loadComponent(); render(EntityNav, { props: baseProps({ inviteCount: 7 }) }); expect(document.body.textContent).toContain('7'); }); });