Files
familienarchiv/frontend/src/routes/layout.svelte.spec.ts
2026-04-20 07:45:16 +02:00

106 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, 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' }));
afterEach(cleanup);
const emptySnippet = createRawSnippet(() => ({ render: () => '<span></span>' }));
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,
...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();
});
});