import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import { Input } from '../input'
import { Search, Eye, EyeOff } from 'lucide-react'
describe('Input Component', () => {
it('renders correctly with default props', () => {
render()
const input = screen.getByPlaceholderText('Enter text')
expect(input).toBeInTheDocument()
expect(input).toHaveClass('h-10')
expect(input).toHaveClass('w-full')
expect(input).toHaveClass('text-sm')
})
it('handles value changes', () => {
const handleChange = jest.fn()
render()
const input = screen.getByRole('textbox')
fireEvent.change(input, { target: { value: 'test value' } })
expect(handleChange).toHaveBeenCalled()
expect(input).toHaveValue('test value')
})
it('handles different input types', () => {
const { rerender } = render()
expect(screen.getByTestId('input')).toHaveAttribute('type', 'password')
rerender()
expect(screen.getByTestId('input')).toHaveAttribute('type', 'email')
rerender()
expect(screen.getByTestId('input')).toHaveAttribute('type', 'number')
})
it('handles disabled state', () => {
render()
const input = screen.getByRole('textbox')
expect(input).toBeDisabled()
expect(input).toHaveClass('disabled:cursor-not-allowed')
})
it('applies custom className', () => {
render()
expect(screen.getByRole('textbox')).toHaveClass('custom-input')
})
it('forwards ref correctly', () => {
const ref = jest.fn()
render()
expect(ref).toHaveBeenCalled()
})
it('handles focus and blur events', () => {
const handleFocus = jest.fn()
const handleBlur = jest.fn()
render()
const input = screen.getByRole('textbox')
fireEvent.focus(input)
expect(handleFocus).toHaveBeenCalled()
fireEvent.blur(input)
expect(handleBlur).toHaveBeenCalled()
})
it('supports controlled component pattern', () => {
const TestComponent = () => {
const [value, setValue] = React.useState('')
return (
setValue(e.target.value)}
data-testid="controlled-input"
/>
)
}
render()
const input = screen.getByTestId('controlled-input') as HTMLInputElement
fireEvent.change(input, { target: { value: 'controlled' } })
expect(input.value).toBe('controlled')
})
describe('Variants', () => {
it('renders standard variant correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('border')
expect(input).toHaveClass('border-gray-300')
expect(input).toHaveClass('rounded-md')
})
it('renders filled variant correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('bg-gray-100')
expect(input).toHaveClass('border-transparent')
})
it('renders ghost variant correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('border-none')
expect(input).toHaveClass('bg-transparent')
})
it('renders underline variant correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('border-b')
expect(input).toHaveClass('rounded-none')
})
})
describe('Sizes', () => {
it('renders small size correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('h-8')
expect(input).toHaveClass('text-xs')
})
it('renders medium size correctly (default)', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('h-10')
expect(input).toHaveClass('text-sm')
})
it('renders large size correctly', () => {
render()
const input = screen.getByTestId('input')
expect(input).toHaveClass('h-12')
expect(input).toHaveClass('text-base')
})
})
describe('Icons and Buttons', () => {
it('renders with left icon', () => {
render(} />)
expect(screen.getByTestId('search-icon')).toBeInTheDocument()
expect(screen.getByRole('textbox')).toHaveClass('pl-10')
})
it('renders with right icon', () => {
render(} />)
expect(screen.getByTestId('search-icon')).toBeInTheDocument()
expect(screen.getByRole('textbox')).toHaveClass('pr-10')
})
it('renders with right button', () => {
const button =
render()
expect(screen.getByTestId('toggle-btn')).toBeInTheDocument()
expect(screen.getByRole('textbox')).toHaveClass('pr-10')
})
it('handles password toggle functionality', () => {
const PasswordInput = () => {
const [showPassword, setShowPassword] = React.useState(false)
return (
setShowPassword(!showPassword)}
data-testid="password-toggle"
>
{showPassword ? : }
}
/>
)
}
render()
const input = screen.getByDisplayValue('') // Get input by display value
const toggleBtn = screen.getByTestId('password-toggle')
// Initially should be password type
expect(input).toHaveAttribute('type', 'password')
fireEvent.click(toggleBtn)
expect(input).toHaveAttribute('type', 'text')
})
})
describe('Loading State', () => {
it('shows loading spinner when loading is true', () => {
render()
expect(screen.getByRole('textbox')).toBeDisabled()
expect(screen.getByRole('textbox')).toHaveAttribute('data-loading', '')
})
it('shows loading spinner with left icon', () => {
render(} />)
const input = screen.getByRole('textbox')
expect(input).toBeDisabled()
expect(input).toHaveClass('pl-10')
})
it('shows loading spinner without left icon', () => {
render()
const input = screen.getByRole('textbox')
expect(input).toBeDisabled()
expect(input).toHaveClass('pl-10')
})
it('hides right icon when loading', () => {
render(} />)
expect(screen.queryByTestId('search-icon')).not.toBeInTheDocument()
})
})
describe('Error and Success States', () => {
it('displays error message', () => {
render()
const input = screen.getByRole('textbox')
const errorMessage = screen.getByText('This field is required')
expect(input).toHaveAttribute('aria-invalid', 'true')
expect(input).toHaveAttribute('aria-describedby', 'test-input-error')
expect(input).toHaveAttribute('data-error', '')
expect(input).toHaveClass('border-error')
expect(errorMessage).toHaveClass('text-error')
expect(errorMessage).toHaveAttribute('id', 'test-input-error')
})
it('displays success message', () => {
render()
const input = screen.getByRole('textbox')
const successMessage = screen.getByText('Valid input')
expect(input).toHaveAttribute('aria-describedby', 'test-input-success')
expect(input).toHaveAttribute('data-success', '')
expect(input).toHaveClass('border-success')
expect(successMessage).toHaveClass('text-success')
expect(successMessage).toHaveAttribute('id', 'test-input-success')
})
it('prioritizes error over success', () => {
render()
expect(screen.getByText('Error message')).toBeInTheDocument()
expect(screen.queryByText('Success message')).not.toBeInTheDocument()
})
it('shows message when alwaysShowMessage is true', () => {
render()
// Should show message container with success message
const messageContainer = screen.getByText('Test message')
expect(messageContainer).toBeInTheDocument()
expect(messageContainer).toHaveClass('text-success')
})
it('applies custom message className', () => {
render()
const message = screen.getByText('Error')
expect(message).toHaveClass('custom-message')
})
})
describe('Custom Styling', () => {
it('applies custom wrapper className', () => {
render(
)
const wrapper = screen.getByTestId('input').parentElement
expect(wrapper).toHaveClass('custom-wrapper')
})
it('combines multiple class names correctly', () => {
render(
)
const input = screen.getByTestId('input')
expect(input).toHaveClass('custom-input')
expect(input).toHaveClass('bg-gray-100')
expect(input).toHaveClass('h-12')
expect(input).toHaveClass('border-error')
})
})
describe('Accessibility', () => {
it('has proper ARIA attributes for error state', () => {
render()
const input = screen.getByRole('textbox')
expect(input).toHaveAttribute('aria-invalid', 'true')
expect(input).toHaveAttribute('aria-describedby', 'accessible-input-error')
})
it('has proper ARIA attributes for success state', () => {
render()
const input = screen.getByRole('textbox')
expect(input).toHaveAttribute('aria-describedby', 'accessible-input-success')
})
it('has proper loading indicators', () => {
render()
const spinner = screen.getByRole('textbox').parentElement?.querySelector('[aria-hidden="true"]')
expect(spinner).toBeInTheDocument()
})
})
})