fix(invite): address review cycle 2 feedback
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m32s
CI / Unit & Component Tests (pull_request) Failing after 2m31s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 2m43s
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m32s
CI / Unit & Component Tests (pull_request) Failing after 2m31s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 2m43s
- Narrow isTrustedProxy to RFC 1918 172.16-31.x.x (was 172.x.x.x) - Add @Valid/@NotBlank/@Email to RegisterRequest and @Valid to AuthController - Add FK constraint on invite_token_group_ids.group_id → user_groups(id) - Add back-to-login link and <main> landmark to register error state - Add component test suite for register/+page.svelte (11 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ let showPassword = $state(false);
|
||||
<div class="flex min-h-screen flex-col bg-canvas">
|
||||
<AuthHeader />
|
||||
|
||||
<div class="flex flex-1 items-center justify-center px-4 py-8">
|
||||
<main class="flex flex-1 items-center justify-center px-4 py-8">
|
||||
<div class="w-full max-w-sm">
|
||||
<div class="mb-10 text-center">
|
||||
<a href="/" class="inline-flex items-center" aria-label="Familienarchiv">
|
||||
@@ -53,7 +53,13 @@ let showPassword = $state(false);
|
||||
<h1 class="mb-2 font-sans text-sm font-bold tracking-widest text-ink uppercase">
|
||||
{m.register_invalid_code()}
|
||||
</h1>
|
||||
<p class="font-serif text-sm text-ink-2">{m.register_invalid_code_desc()}</p>
|
||||
<p class="mb-6 font-serif text-sm text-ink-2">{m.register_invalid_code_desc()}</p>
|
||||
<a
|
||||
href="/login"
|
||||
class="font-sans text-xs font-bold tracking-widest text-brand-navy/60 uppercase transition-colors hover:text-brand-navy"
|
||||
>
|
||||
{m.forgot_password_back_to_login()}
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="rounded-sm border border-line bg-surface p-8 shadow-sm">
|
||||
@@ -185,7 +191,7 @@ let showPassword = $state(false);
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="py-4 text-center">
|
||||
<p class="font-sans text-xs tracking-widest text-ink-3 uppercase">Familienarchiv</p>
|
||||
|
||||
106
frontend/src/routes/register/page.svelte.spec.ts
Normal file
106
frontend/src/routes/register/page.svelte.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import RegisterPage from './+page.svelte';
|
||||
|
||||
const tick = () => new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const validData = {
|
||||
code: 'ABCDE12345',
|
||||
prefill: null,
|
||||
codeError: null
|
||||
};
|
||||
|
||||
const prefillData = {
|
||||
code: 'ABCDE12345',
|
||||
prefill: { firstName: 'Max', lastName: 'Muster', email: 'max@test.com' },
|
||||
codeError: null
|
||||
};
|
||||
|
||||
const errorData = {
|
||||
code: null,
|
||||
prefill: null,
|
||||
codeError: 'INVITE_NOT_FOUND'
|
||||
};
|
||||
|
||||
describe('Register page – valid code', () => {
|
||||
it('renders the heading', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await expect.element(page.getByRole('heading', { level: 1 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the email input', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await tick();
|
||||
const input = document.querySelector<HTMLInputElement>('input[name="email"]');
|
||||
expect(input).not.toBeNull();
|
||||
expect(input?.type).toBe('email');
|
||||
expect(input?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the password input', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await tick();
|
||||
const input = document.querySelector<HTMLInputElement>('input[name="password"]');
|
||||
expect(input).not.toBeNull();
|
||||
expect(input?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the submit button', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await expect.element(page.getByRole('button', { name: 'Konto erstellen' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('prefills fields from invite data', async () => {
|
||||
render(RegisterPage, { data: prefillData });
|
||||
await tick();
|
||||
const email = document.querySelector<HTMLInputElement>('input[name="email"]');
|
||||
const firstName = document.querySelector<HTMLInputElement>('input[name="firstName"]');
|
||||
const lastName = document.querySelector<HTMLInputElement>('input[name="lastName"]');
|
||||
expect(email?.value).toBe('max@test.com');
|
||||
expect(firstName?.value).toBe('Max');
|
||||
expect(lastName?.value).toBe('Muster');
|
||||
});
|
||||
|
||||
it('has a hidden code input', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await tick();
|
||||
const hidden = document.querySelector<HTMLInputElement>('input[name="code"][type="hidden"]');
|
||||
expect(hidden).not.toBeNull();
|
||||
expect(hidden?.value).toBe('ABCDE12345');
|
||||
});
|
||||
|
||||
it('shows form error when action returns error', async () => {
|
||||
render(RegisterPage, { data: validData, form: { error: 'INVITE_REVOKED' } });
|
||||
await tick();
|
||||
expect(document.querySelector('.text-red-600')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('has main landmark', async () => {
|
||||
render(RegisterPage, { data: validData });
|
||||
await tick();
|
||||
expect(document.querySelector('main')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Register page – error state', () => {
|
||||
it('renders the error card when codeError is set', async () => {
|
||||
render(RegisterPage, { data: errorData });
|
||||
await tick();
|
||||
expect(document.querySelector('form')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows a back-to-login link in error state', async () => {
|
||||
render(RegisterPage, { data: errorData });
|
||||
await expect.element(page.getByRole('link', { name: /login/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('back-to-login link points to /login', async () => {
|
||||
render(RegisterPage, { data: errorData });
|
||||
await tick();
|
||||
const link = document.querySelector<HTMLAnchorElement>('a[href="/login"]');
|
||||
expect(link).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user