diff --git a/frontend/src/lib/auth/LoginForm.svelte b/frontend/src/lib/auth/LoginForm.svelte new file mode 100644 index 0000000..166bf51 --- /dev/null +++ b/frontend/src/lib/auth/LoginForm.svelte @@ -0,0 +1,154 @@ + + +
diff --git a/frontend/src/lib/auth/LoginForm.test.ts b/frontend/src/lib/auth/LoginForm.test.ts new file mode 100644 index 0000000..d338b02 --- /dev/null +++ b/frontend/src/lib/auth/LoginForm.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import { userEvent } from '@testing-library/user-event'; +import LoginForm from './LoginForm.svelte'; + +vi.mock('$app/forms', () => ({ + enhance: () => ({ destroy: () => {} }) +})); + +describe('LoginForm', () => { + it('renders email and password fields with correct labels', () => { + render(LoginForm); + expect(screen.getByLabelText('E-Mail')).toBeInTheDocument(); + expect(screen.getByLabelText('Passwort')).toBeInTheDocument(); + }); + + it('renders heading and subtitle', () => { + render(LoginForm); + expect(screen.getByText('Willkommen zurück')).toBeInTheDocument(); + expect(screen.getByText('Melde dich an, um fortzufahren.')).toBeInTheDocument(); + }); + + it('renders submit button', () => { + render(LoginForm); + expect(screen.getByRole('button', { name: /anmelden/i })).toBeInTheDocument(); + }); + + it('renders signup link', () => { + render(LoginForm); + const link = screen.getByRole('link', { name: /registrieren/i }); + expect(link).toHaveAttribute('href', '/signup'); + expect(link.className).toContain('text-[var(--green)]'); + expect(link.className).toContain('font-medium'); + }); + + it('submit button uses --green-dark for WCAG AA', () => { + render(LoginForm); + const button = screen.getByRole('button', { name: /anmelden/i }); + expect(button.className).toContain('bg-[var(--green-dark)]'); + }); + + it('password field is initially of type password', () => { + render(LoginForm); + expect(screen.getByLabelText('Passwort')).toHaveAttribute('type', 'password'); + }); + + it('password toggle switches type', async () => { + const user = userEvent.setup(); + render(LoginForm); + + 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('inputs have correct autocomplete attributes', () => { + render(LoginForm); + expect(screen.getByLabelText('E-Mail')).toHaveAttribute('autocomplete', 'email'); + expect(screen.getByLabelText('Passwort')).toHaveAttribute('autocomplete', 'current-password'); + }); + + it('shows validation error for invalid email on submit', async () => { + const user = userEvent.setup(); + render(LoginForm); + + await user.type(screen.getByLabelText('E-Mail'), 'notanemail'); + await user.type(screen.getByLabelText('Passwort'), 'password123'); + await user.click(screen.getByRole('button', { name: /anmelden/i })); + + expect(screen.getByText('Ungültige E-Mail-Adresse')).toBeInTheDocument(); + }); + + it('shows validation error for empty password on submit', async () => { + const user = userEvent.setup(); + render(LoginForm); + + await user.type(screen.getByLabelText('E-Mail'), 'test@example.com'); + await user.click(screen.getByRole('button', { name: /anmelden/i })); + + expect(screen.getByText('Passwort ist erforderlich')).toBeInTheDocument(); + }); + + it('shows no errors when fields are valid', async () => { + const user = userEvent.setup(); + render(LoginForm); + + await user.type(screen.getByLabelText('E-Mail'), 'test@example.com'); + await user.type(screen.getByLabelText('Passwort'), 'password123'); + await user.click(screen.getByRole('button', { name: /anmelden/i })); + + expect(screen.queryByText('Ungültige E-Mail-Adresse')).not.toBeInTheDocument(); + expect(screen.queryByText('Passwort ist erforderlich')).not.toBeInTheDocument(); + }); + + it('displays server-side form error from form prop', () => { + render(LoginForm, { + props: { + form: { + errors: { form: 'E-Mail oder Passwort ist falsch.' }, + email: 'test@example.com' + } + } + }); + expect(screen.getByText('E-Mail oder Passwort ist falsch.')).toBeInTheDocument(); + }); + + it('renders placeholders', () => { + render(LoginForm); + expect(screen.getByPlaceholderText('du@beispiel.de')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Dein Passwort')).toBeInTheDocument(); + }); +});