Files
familienarchiv/frontend/src/routes/register/page.svelte.test.ts

222 lines
7.8 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 { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import RegisterPage from './+page.svelte';
afterEach(cleanup);
const baseData = (overrides: Record<string, unknown> = {}) => ({
code: 'invite-token',
prefill: null as { firstName?: string; lastName?: string; email?: string } | null,
codeError: null as string | null,
...overrides
});
describe('register page', () => {
it('renders the hero headline when there is no codeError', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
await expect
.element(page.getByRole('heading', { name: /schön, dass du da bist/i }))
.toBeVisible();
});
it('renders the NO_INVITE_CODE error card with invite-only heading', async () => {
render(RegisterPage, {
props: { data: baseData({ codeError: 'NO_INVITE_CODE' }), form: undefined }
});
await expect.element(page.getByRole('heading', { name: /nur auf einladung/i })).toBeVisible();
});
it('renders the invalid-code error card for any other codeError value', async () => {
render(RegisterPage, {
props: { data: baseData({ codeError: 'EXPIRED' }), form: undefined }
});
await expect
.element(page.getByRole('heading', { name: /ungültiger einladungslink/i }))
.toBeVisible();
});
it('hides the form when there is a codeError', async () => {
render(RegisterPage, {
props: { data: baseData({ codeError: 'NO_INVITE_CODE' }), form: undefined }
});
await expect.element(page.getByLabelText(/^vorname$/i)).not.toBeInTheDocument();
});
it('shows the back-to-login link in the codeError card', async () => {
render(RegisterPage, {
props: { data: baseData({ codeError: 'NO_INVITE_CODE' }), form: undefined }
});
await expect
.element(page.getByRole('link', { name: /zurück zum login/i }))
.toHaveAttribute('href', '/login');
});
it('renders the form sections when there is no codeError', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
await expect.element(page.getByText('Über dich')).toBeVisible();
await expect.element(page.getByLabelText(/vorname/i)).toBeVisible();
await expect.element(page.getByLabelText(/^e-mail-adresse$/i)).toBeVisible();
});
it('hydrates the form fields from prefill', async () => {
render(RegisterPage, {
props: {
data: baseData({
prefill: { firstName: 'Anna', lastName: 'Schmidt', email: 'anna@example.com' }
}),
form: undefined
}
});
const firstName = (await page.getByLabelText(/vorname/i).element()) as HTMLInputElement;
const lastName = (await page.getByLabelText(/nachname/i).element()) as HTMLInputElement;
const email = (await page.getByLabelText(/^e-mail-adresse$/i).element()) as HTMLInputElement;
expect(firstName.value).toBe('Anna');
expect(lastName.value).toBe('Schmidt');
expect(email.value).toBe('anna@example.com');
});
it('shows the prefill hint when an email is prefilled', async () => {
render(RegisterPage, {
props: {
data: baseData({ prefill: { email: 'anna@example.com' } }),
form: undefined
}
});
await expect
.element(page.getByText('Von deiner Einladung übernommen du kannst es ändern'))
.toBeVisible();
});
it('omits the prefill hint when no email is prefilled', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
await expect
.element(page.getByText('Von deiner Einladung übernommen du kannst es ändern'))
.not.toBeInTheDocument();
});
it('puts the invite code in a hidden input', async () => {
render(RegisterPage, { props: { data: baseData({ code: 'tok-99' }), form: undefined } });
const codeInput = document.querySelector('input[name="code"]') as HTMLInputElement;
expect(codeInput.type).toBe('hidden');
expect(codeInput.value).toBe('tok-99');
});
it('renders an empty hidden code input when data.code is null', async () => {
render(RegisterPage, { props: { data: baseData({ code: null }), form: undefined } });
const codeInput = document.querySelector('input[name="code"]') as HTMLInputElement;
expect(codeInput.value).toBe('');
});
it('toggles password visibility when the show/hide button is clicked', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const pw = document.querySelector('input[name="password"]') as HTMLInputElement;
expect(pw.type).toBe('password');
const toggle = pw.parentElement?.querySelector('button[type="button"]') as HTMLButtonElement;
toggle.click();
await vi.waitFor(() => expect(pw.type).toBe('text'));
toggle.click();
await vi.waitFor(() => expect(pw.type).toBe('password'));
});
it('toggles passwordConfirm visibility independently from password', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const confirm = document.querySelector('#passwordConfirm') as HTMLInputElement;
expect(confirm.type).toBe('password');
const toggle = confirm.parentElement?.querySelector(
'button[type="button"]'
) as HTMLButtonElement;
toggle.click();
await vi.waitFor(() => expect(confirm.type).toBe('text'));
});
it('shows the password length hint after typing a short password', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const pw = document.querySelector('input[name="password"]') as HTMLInputElement;
pw.value = 'abc';
pw.dispatchEvent(new Event('input', { bubbles: true }));
await expect.element(page.getByText(/mindestens 8 zeichen/i)).toBeVisible();
});
it('shows the password OK hint when password is at least 8 chars', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const pw = document.querySelector('input[name="password"]') as HTMLInputElement;
pw.value = 'longenoughpw';
pw.dispatchEvent(new Event('input', { bubbles: true }));
await vi.waitFor(() => {
expect(document.querySelectorAll('.text-green-700').length).toBeGreaterThan(0);
});
});
it('shows the password mismatch error when confirm differs from password', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const pw = document.querySelector('input[name="password"]') as HTMLInputElement;
const confirm = document.querySelector('#passwordConfirm') as HTMLInputElement;
pw.value = 'longenoughpw';
pw.dispatchEvent(new Event('input', { bubbles: true }));
confirm.value = 'differentpw';
confirm.dispatchEvent(new Event('input', { bubbles: true }));
await vi.waitFor(() => {
expect(document.querySelectorAll('.text-red-600').length).toBeGreaterThan(0);
});
});
it('shows the password match success when confirm equals password', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const pw = document.querySelector('input[name="password"]') as HTMLInputElement;
const confirm = document.querySelector('#passwordConfirm') as HTMLInputElement;
pw.value = 'longenoughpw';
pw.dispatchEvent(new Event('input', { bubbles: true }));
confirm.value = 'longenoughpw';
confirm.dispatchEvent(new Event('input', { bubbles: true }));
await vi.waitFor(() => {
// Both "pw OK" and "match OK" hints render with text-green-700.
expect(document.querySelectorAll('.text-green-700').length).toBeGreaterThanOrEqual(2);
});
});
it('renders the form error from the form prop', async () => {
render(RegisterPage, {
props: { data: baseData(), form: { error: 'EMAIL_TAKEN' } }
});
const errorBox = document.querySelector('.text-red-600.font-medium');
expect(errorBox).not.toBeNull();
});
it('toggles the notifyOnMention checkbox', async () => {
render(RegisterPage, { props: { data: baseData(), form: undefined } });
const cb = document.querySelector('input[name="notifyOnMention"]') as HTMLInputElement;
expect(cb.checked).toBe(true);
cb.click();
await vi.waitFor(() => expect(cb.checked).toBe(false));
});
});