import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, it, expect, vi } from 'vitest' import { axe } from 'vitest-axe' import * as React from 'react' import { Input } from './input' describe('Input', () => { it('renders with textbox role', () => { render() expect(screen.getByRole('textbox', { name: 'Email' })).toBeInTheDocument() }) it('accepts user typing', async () => { const user = userEvent.setup() render() const input = screen.getByRole('textbox') await user.type(input, 'Roman') expect(input).toHaveValue('Roman') }) it('calls onChange when typing in a controlled input', async () => { const user = userEvent.setup() const onChange = vi.fn() function Controlled() { const [value, setValue] = React.useState('') return ( { setValue(e.target.value) onChange(e.target.value) }} /> ) } render() await user.type(screen.getByRole('textbox'), 'hi') expect(onChange).toHaveBeenCalledWith('hi') expect(screen.getByRole('textbox')).toHaveValue('hi') }) it('is disabled when the disabled prop is set', async () => { const user = userEvent.setup() render() const input = screen.getByRole('textbox') expect(input).toBeDisabled() await user.type(input, 'x') expect(input).toHaveValue('') }) it('applies size classes', () => { render() expect(screen.getByRole('textbox').className).toContain('h-12') }) it('applies variant classes', () => { render() expect(screen.getByRole('textbox').className).toContain('bg-background-muted') }) it('forwards an outer ref', () => { const ref = React.createRef() render() expect(ref.current).toBeInstanceOf(HTMLInputElement) }) it('renders the clear button when clearable and clears the value', async () => { const user = userEvent.setup() const onClear = vi.fn() render() const input = screen.getByRole('textbox') await user.type(input, 'query') expect(input).toHaveValue('query') await user.click(screen.getByRole('button', { name: 'Clear input' })) expect(input).toHaveValue('') expect(onClear).toHaveBeenCalledOnce() }) it('renders startSlot and endSlot when provided', () => { render( $} endSlot={USD} />, ) expect(screen.getByTestId('start')).toBeInTheDocument() expect(screen.getByTestId('end')).toBeInTheDocument() }) it('bridges aria-invalid to data-invalid on the bare input', () => { const { container } = render() const input = container.querySelector('[data-slot="input"]') expect(input).toHaveAttribute('aria-invalid', 'true') expect(input).toHaveAttribute('data-invalid') }) it('bridges aria-invalid to data-invalid on the wrapper when slots are present', () => { const { container } = render(@} />) expect(container.querySelector('[data-slot="input-wrapper"]')).toHaveAttribute('data-invalid') }) it('does not set data-invalid when valid', () => { const { container } = render() expect(container.querySelector('[data-slot="input"]')).not.toHaveAttribute('data-invalid') }) it('has no accessibility violations', async () => { const { container } = render() expect(await axe(container)).toHaveNoViolations() }) })