import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page, userEvent } from 'vitest/browser'; import { createRawSnippet } from 'svelte'; vi.mock('$env/static/public', () => ({ PUBLIC_NOTIFICATION_POLL_MS: '60000' })); // NotificationBell calls notificationStore.init() on mount, which creates an // EventSource and immediately fetches the unread count. In the test environment // the SSE connection fails (no backend), triggering onerror → another fetch. // If that fetch returns 401, notifications.svelte.ts calls // window.location.href = '/login', navigating the test iframe and breaking the // Playwright connection. Stubbing fetch to return 200 keeps it in-check. beforeEach(() => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, json: vi.fn().mockResolvedValue({ count: 0, content: [] }) }) ); }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); }); const emptySnippet = createRawSnippet(() => ({ render: () => '' })); import Layout from './+layout.svelte'; const tick = () => new Promise((r) => setTimeout(r, 0)); // Minimal data required by the layout const makeData = (overrides = {}) => ({ user: { id: '1', firstName: 'Max', lastName: 'Müller', email: 'max@example.com', groups: [], enabled: true, createdAt: '' }, canWrite: true, canAnnotate: false, canBlogWrite: false, ...overrides }); // ─── User avatar ────────────────────────────────────────────────────────────── describe('Layout – user avatar button', () => { it('shows user initials when first and last name are set', async () => { render(Layout, { data: makeData(), children: emptySnippet }); await expect.element(page.getByRole('button', { name: /MM/ })).toBeInTheDocument(); }); it('shows fallback icon button when names are not set', async () => { render(Layout, { data: makeData({ user: { id: '1', email: 'fallback@example.com', groups: [], enabled: true, createdAt: '' } }), children: emptySnippet }); // Button should still exist (with aria-label for accessibility) await expect.element(page.getByRole('button', { name: /Profil/i })).toBeInTheDocument(); }); }); // ─── Upload link ────────────────────────────────────────────────────────────── describe('Layout – upload link', () => { it('has aria-label for screen reader access', async () => { render(Layout, { data: makeData(), children: emptySnippet }); const link = page.getByRole('link', { name: /Hochladen|Upload|Subir/i }); await expect.element(link).toHaveAttribute('aria-label'); }); it('navigates to /documents/new', async () => { render(Layout, { data: makeData(), children: emptySnippet }); const link = page.getByRole('link', { name: /Hochladen|Upload|Subir/i }); await expect.element(link).toHaveAttribute('href', '/documents/new'); }); }); // ─── Dropdown ───────────────────────────────────────────────────────────────── describe('Layout – user dropdown', () => { it('dropdown is hidden initially', async () => { render(Layout, { data: makeData(), children: emptySnippet }); await tick(); await expect.element(page.getByRole('link', { name: /Profil/i })).not.toBeInTheDocument(); }); it('opens dropdown on button click', async () => { render(Layout, { data: makeData(), children: emptySnippet }); await page.getByRole('button', { name: /MM/ }).click(); await expect.element(page.getByRole('link', { name: /Profil/i })).toBeInTheDocument(); }); it('profile link points to /profile', async () => { render(Layout, { data: makeData(), children: emptySnippet }); await page.getByRole('button', { name: /MM/ }).click(); await expect .element(page.getByRole('link', { name: /Profil/i })) .toHaveAttribute('href', '/profile'); }); it('logout button is in the dropdown', async () => { render(Layout, { data: makeData(), children: emptySnippet }); await page.getByRole('button', { name: /MM/ }).click(); await expect.element(page.getByRole('button', { name: /Abmelden/i })).toBeInTheDocument(); }); it('closes dropdown when Escape is pressed', async () => { render(Layout, { data: makeData(), children: emptySnippet }); const btn = page.getByRole('button', { name: /MM/ }); await btn.click(); await expect.element(page.getByRole('link', { name: /Profil/i })).toBeInTheDocument(); await userEvent.keyboard('{Escape}'); await tick(); await expect.element(page.getByRole('link', { name: /Profil/i })).not.toBeInTheDocument(); }); });