test(coverage): drive browser tests to 80% on all metrics (#496) #505

Merged
marcel merged 189 commits from feat/issue-496-browser-coverage-tests into main 2026-05-11 21:50:39 +02:00
2 changed files with 142 additions and 0 deletions
Showing only changes of commit 8f15eea78d - Show all commits

View File

@@ -0,0 +1,83 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import DashboardFamilyPulse from './DashboardFamilyPulse.svelte';
afterEach(cleanup);
const basePulse = (overrides: Record<string, unknown> = {}) => ({
pages: 0,
yourPages: 0,
contributors: [] as { initials: string; color: string; name?: string | null }[],
annotated: 0,
transcribed: 0,
uploaded: 0,
reviewed: 0,
...overrides
});
describe('DashboardFamilyPulse', () => {
it('renders nothing when pulse is null', async () => {
render(DashboardFamilyPulse, { props: { pulse: null } });
expect(document.querySelector('section')).toBeNull();
});
it('renders the eyebrow when pulse is not null', async () => {
render(DashboardFamilyPulse, { props: { pulse: basePulse() } });
await expect.element(page.getByText('Diese Woche')).toBeVisible();
});
it('hides the headline when pages is 0', async () => {
render(DashboardFamilyPulse, { props: { pulse: basePulse({ pages: 0 }) } });
await expect.element(page.getByRole('heading')).not.toBeInTheDocument();
});
it('renders the headline when pages > 0', async () => {
render(DashboardFamilyPulse, { props: { pulse: basePulse({ pages: 12 }) } });
await expect.element(page.getByText(/12 Seiten bearbeitet/)).toBeVisible();
});
it('renders the "you" line only when yourPages > 0', async () => {
render(DashboardFamilyPulse, { props: { pulse: basePulse({ yourPages: 3 }) } });
await expect.element(page.getByText(/3 davon bearbeitet/)).toBeVisible();
});
it('omits the contributors section when there are none', async () => {
render(DashboardFamilyPulse, { props: { pulse: basePulse() } });
await expect.element(page.getByText('Mitwirkende')).not.toBeInTheDocument();
});
it('renders one chip per contributor', async () => {
render(DashboardFamilyPulse, {
props: {
pulse: basePulse({
contributors: [
{ initials: 'AS', color: '#012851', name: 'Anna Schmidt' },
{ initials: 'BM', color: '#5a3080', name: 'Bert Meier' }
]
})
}
});
await expect.element(page.getByText('AS')).toBeVisible();
await expect.element(page.getByText('BM')).toBeVisible();
});
it('renders the three count tiles', async () => {
render(DashboardFamilyPulse, {
props: {
pulse: basePulse({ annotated: 15, transcribed: 7, uploaded: 3 })
}
});
await expect.element(page.getByText('15')).toBeVisible();
await expect.element(page.getByText('7')).toBeVisible();
await expect.element(page.getByText('3')).toBeVisible();
});
});

View File

@@ -0,0 +1,59 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import UserMenu from './UserMenu.svelte';
afterEach(cleanup);
describe('UserMenu', () => {
it('renders the avatar button when userInitials is set', async () => {
render(UserMenu, { props: { userInitials: 'AS' } });
await expect.element(page.getByRole('button', { name: 'AS' })).toBeVisible();
});
it('renders the icon-only button when userInitials is null', async () => {
render(UserMenu, { props: { userInitials: null } });
await expect.element(page.getByRole('button', { name: /profil/i })).toBeVisible();
});
it('starts with the menu closed (aria-expanded=false)', async () => {
render(UserMenu, { props: { userInitials: 'AS' } });
await expect
.element(page.getByRole('button', { name: 'AS' }))
.toHaveAttribute('aria-expanded', 'false');
});
it('opens the menu when the trigger is clicked', async () => {
render(UserMenu, { props: { userInitials: 'AS' } });
await page.getByRole('button', { name: 'AS' }).click();
await expect
.element(page.getByRole('button', { name: 'AS' }))
.toHaveAttribute('aria-expanded', 'true');
});
it('renders the profile link and logout button when the menu is open', async () => {
render(UserMenu, { props: { userInitials: 'AS' } });
await page.getByRole('button', { name: 'AS' }).click();
await expect
.element(page.getByRole('link', { name: /profil/i }))
.toHaveAttribute('href', '/profile');
await expect.element(page.getByRole('button', { name: /abmelden/i })).toBeVisible();
});
it('declares POST and /logout on the logout form', async () => {
render(UserMenu, { props: { userInitials: 'AS' } });
await page.getByRole('button', { name: 'AS' }).click();
const form = document.querySelector('form[action="/logout"]') as HTMLFormElement;
expect(form).not.toBeNull();
expect(form.method.toLowerCase()).toBe('post');
});
});