test: add validation and interaction tests for LoginForm

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 11:14:05 +01:00
parent bda0d11fde
commit ec0db5a011

View File

@@ -1,5 +1,6 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
const socialProviders = [
@@ -73,4 +74,120 @@ describe('LoginForm', () => {
expect(screen.getByText('Invalid credentials')).toBeInTheDocument()
})
})
describe('validation', () => {
it('validates required email', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(screen.getByText('Email is required')).toBeInTheDocument()
})
it('validates email format', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.type(screen.getByLabelText(/email/i), 'notanemail')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(screen.getByText('Please enter a valid email address')).toBeInTheDocument()
})
it('validates required password', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.type(screen.getByLabelText(/email/i), 'test@example.com')
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(screen.getByText('Password is required')).toBeInTheDocument()
})
it('validates password minimum length', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.type(screen.getByLabelText(/email/i), 'test@example.com')
await user.type(screen.getByLabelText(/password/i), 'short')
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(screen.getByText('Password must be at least 8 characters')).toBeInTheDocument()
})
it('clears field errors on typing', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(screen.getByText('Email is required')).toBeInTheDocument()
await user.type(screen.getByLabelText(/email/i), 't')
expect(screen.queryByText('Email is required')).not.toBeInTheDocument()
})
it('calls onSubmit with credentials when valid', async () => {
const onSubmit = vi.fn()
const user = userEvent.setup()
render(<LoginForm onSubmit={onSubmit} />)
await user.type(screen.getByLabelText(/email/i), 'test@example.com')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByLabelText(/remember me/i))
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
remember: true,
})
})
it('does not call onSubmit when validation fails', async () => {
const onSubmit = vi.fn()
const user = userEvent.setup()
render(<LoginForm onSubmit={onSubmit} />)
await user.click(screen.getByRole('button', { name: 'Sign in' }))
expect(onSubmit).not.toHaveBeenCalled()
})
})
describe('loading state', () => {
it('disables form inputs when loading', () => {
render(<LoginForm {...allProps} loading />)
expect(screen.getByLabelText(/email/i)).toBeDisabled()
expect(screen.getByLabelText(/password/i)).toBeDisabled()
expect(screen.getByLabelText(/remember me/i)).toBeDisabled()
})
it('shows spinner on submit button when loading', () => {
render(<LoginForm {...allProps} loading />)
const submitBtn = screen.getByRole('button', { name: /sign in/i })
expect(submitBtn).toBeDisabled()
// Button component renders Spinner when loading=true
expect(submitBtn.querySelector('[class*="spinner"]')).toBeInTheDocument()
})
it('disables social buttons when loading', () => {
render(<LoginForm {...allProps} loading />)
expect(screen.getByRole('button', { name: 'Continue with Google' })).toBeDisabled()
expect(screen.getByRole('button', { name: 'Continue with GitHub' })).toBeDisabled()
})
})
describe('callbacks', () => {
it('calls social provider onClick when clicked', async () => {
const onClick = vi.fn()
const user = userEvent.setup()
render(<LoginForm socialProviders={[{ label: 'Continue with Google', onClick }]} onSubmit={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'Continue with Google' }))
expect(onClick).toHaveBeenCalledOnce()
})
it('calls onForgotPassword when link clicked', async () => {
const onForgotPassword = vi.fn()
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} onForgotPassword={onForgotPassword} />)
await user.click(screen.getByText(/forgot password/i))
expect(onForgotPassword).toHaveBeenCalledOnce()
})
it('calls onSignUp when link clicked', async () => {
const onSignUp = vi.fn()
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} onSignUp={onSignUp} />)
await user.click(screen.getByText(/sign up/i))
expect(onSignUp).toHaveBeenCalledOnce()
})
})
})