diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7df3349..2eea8fb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "@tailwindcss/vite": "^4.2.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", + "@testing-library/user-event": "^14.6.1", "@types/node": "^25.5.0", "@vitest/ui": "^4.1.2", "jsdom": "^29.0.1", @@ -1809,6 +1810,20 @@ "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index ee0cbb9..5733cbc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "@tailwindcss/vite": "^4.2.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/svelte": "^5.3.1", + "@testing-library/user-event": "^14.6.1", "@types/node": "^25.5.0", "@vitest/ui": "^4.1.2", "jsdom": "^29.0.1", diff --git a/frontend/src/lib/auth/SignupForm.svelte b/frontend/src/lib/auth/SignupForm.svelte new file mode 100644 index 0000000..5637dd9 --- /dev/null +++ b/frontend/src/lib/auth/SignupForm.svelte @@ -0,0 +1,154 @@ + + +
diff --git a/frontend/src/lib/auth/SignupForm.test.ts b/frontend/src/lib/auth/SignupForm.test.ts new file mode 100644 index 0000000..e7fdddb --- /dev/null +++ b/frontend/src/lib/auth/SignupForm.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import { userEvent } from '@testing-library/user-event'; +import SignupForm from './SignupForm.svelte'; + +describe('SignupForm', () => { + it('renders all form fields with correct labels', () => { + render(SignupForm); + expect(screen.getByLabelText('Dein Name')).toBeInTheDocument(); + expect(screen.getByLabelText('E-Mail')).toBeInTheDocument(); + expect(screen.getByLabelText('Passwort')).toBeInTheDocument(); + }); + + it('renders submit button with correct text', () => { + render(SignupForm); + expect(screen.getByRole('button', { name: /konto erstellen/i })).toBeInTheDocument(); + }); + + it('renders login link', () => { + render(SignupForm); + const link = screen.getByRole('link', { name: /anmelden/i }); + expect(link).toHaveAttribute('href', '/login'); + }); + + it('renders heading and subtitle', () => { + render(SignupForm); + expect(screen.getByText('Konto erstellen')).toBeInTheDocument(); + expect(screen.getByText('Danach richtest du deinen Haushalt ein.')).toBeInTheDocument(); + }); + + it('password field is initially of type password', () => { + render(SignupForm); + const input = screen.getByLabelText('Passwort'); + expect(input).toHaveAttribute('type', 'password'); + }); + + it('password toggle switches to text and back', async () => { + const user = userEvent.setup(); + render(SignupForm); + + const input = screen.getByLabelText('Passwort'); + const toggle = screen.getByRole('button', { name: /passwort anzeigen/i }); + + await user.click(toggle); + expect(input).toHaveAttribute('type', 'text'); + + await user.click(toggle); + expect(input).toHaveAttribute('type', 'password'); + }); + + it('shows validation error for empty name on submit', async () => { + const user = userEvent.setup(); + render(SignupForm); + + const email = screen.getByLabelText('E-Mail'); + const password = screen.getByLabelText('Passwort'); + await user.type(email, 'test@example.com'); + await user.type(password, 'password123'); + + const submit = screen.getByRole('button', { name: /konto erstellen/i }); + await user.click(submit); + + expect(screen.getByText('Name ist erforderlich')).toBeInTheDocument(); + }); + + it('shows validation error for invalid email on submit', async () => { + const user = userEvent.setup(); + render(SignupForm); + + const name = screen.getByLabelText('Dein Name'); + const email = screen.getByLabelText('E-Mail'); + const password = screen.getByLabelText('Passwort'); + await user.type(name, 'Sarah'); + await user.type(email, 'notanemail'); + await user.type(password, 'password123'); + + const submit = screen.getByRole('button', { name: /konto erstellen/i }); + await user.click(submit); + + expect(screen.getByText('Ungültige E-Mail-Adresse')).toBeInTheDocument(); + }); + + it('shows validation error for short password on submit', async () => { + const user = userEvent.setup(); + render(SignupForm); + + const name = screen.getByLabelText('Dein Name'); + const email = screen.getByLabelText('E-Mail'); + const password = screen.getByLabelText('Passwort'); + await user.type(name, 'Sarah'); + await user.type(email, 'test@example.com'); + await user.type(password, 'short'); + + const submit = screen.getByRole('button', { name: /konto erstellen/i }); + await user.click(submit); + + expect(screen.getByText('Mindestens 8 Zeichen')).toBeInTheDocument(); + }); + + it('shows no validation errors when all fields are valid', async () => { + const user = userEvent.setup(); + render(SignupForm); + + const name = screen.getByLabelText('Dein Name'); + const email = screen.getByLabelText('E-Mail'); + const password = screen.getByLabelText('Passwort'); + await user.type(name, 'Sarah'); + await user.type(email, 'test@example.com'); + await user.type(password, 'password123'); + + const submit = screen.getByRole('button', { name: /konto erstellen/i }); + await user.click(submit); + + expect(screen.queryByText('Name ist erforderlich')).not.toBeInTheDocument(); + expect(screen.queryByText('Ungültige E-Mail-Adresse')).not.toBeInTheDocument(); + expect(screen.queryByText('Mindestens 8 Zeichen')).not.toBeInTheDocument(); + }); + + it('renders placeholders on inputs', () => { + render(SignupForm); + expect(screen.getByPlaceholderText('z.B. Sarah')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('du@beispiel.de')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Mindestens 8 Zeichen')).toBeInTheDocument(); + }); +});