222 lines
7.8 KiB
TypeScript
222 lines
7.8 KiB
TypeScript
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));
|
||
});
|
||
});
|