import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { ListCard } from '../ListCard' import { CreateListDialog } from '../CreateListDialog' // --------------------------------------------------------------------------- // ListCard // --------------------------------------------------------------------------- describe('ListCard', () => { const baseProps = { name: 'My Prospect List', sourceType: 'static', memberCount: 42, createdAt: 'Jan 1, 2025', } it('renders the list name', () => { render() expect(screen.getByText('My Prospect List')).toBeInTheDocument() }) it('renders member count with singular "member"', () => { render() expect(screen.getByText('1 member')).toBeInTheDocument() }) it('renders member count with plural "members"', () => { render() expect(screen.getByText('42 members')).toBeInTheDocument() }) it('renders zero members correctly', () => { render() expect(screen.getByText('0 members')).toBeInTheDocument() }) it('renders large member count with locale formatting', () => { render() expect(screen.getByText('1,500 members')).toBeInTheDocument() }) it('renders the source type badge', () => { render() expect(screen.getByText('static')).toBeInTheDocument() }) it('renders the createdAt text', () => { render() expect(screen.getByText('Created Jan 1, 2025')).toBeInTheDocument() }) it('applies blue badge styles for static source type', () => { render() const badge = screen.getByText('static') expect(badge).toHaveClass('bg-blue-100', 'text-blue-700') }) it('applies purple badge styles for funnel source type', () => { render() const badge = screen.getByText('funnel') expect(badge).toHaveClass('bg-purple-100', 'text-purple-700') }) it('applies green badge styles for query source type', () => { render() const badge = screen.getByText('query') expect(badge).toHaveClass('bg-green-100', 'text-green-700') }) it('applies gray badge for unknown source type', () => { render() const badge = screen.getByText('unknown-type') expect(badge).toHaveClass('bg-gray-100', 'text-gray-700') }) it('has role=button when onClick is provided', () => { render() expect(screen.getByRole('button')).toBeInTheDocument() }) it('does not have role=button when onClick is not provided', () => { render() expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('calls onClick when clicked', () => { const onClick = jest.fn() render() fireEvent.click(screen.getByRole('button')) expect(onClick).toHaveBeenCalledTimes(1) }) it('calls onClick when Enter key is pressed', () => { const onClick = jest.fn() render() fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' }) expect(onClick).toHaveBeenCalledTimes(1) }) it('calls onClick when Space key is pressed', () => { const onClick = jest.fn() render() fireEvent.keyDown(screen.getByRole('button'), { key: ' ' }) expect(onClick).toHaveBeenCalledTimes(1) }) it('applies custom className', () => { const { container } = render() expect(container.firstChild).toHaveClass('custom-card') }) }) // --------------------------------------------------------------------------- // CreateListDialog // --------------------------------------------------------------------------- describe('CreateListDialog', () => { const baseProps = { open: true, onClose: jest.fn(), onSubmit: jest.fn(), } beforeEach(() => { jest.clearAllMocks() }) it('renders nothing when open is false', () => { render() expect(screen.queryByText('Create List')).not.toBeInTheDocument() }) it('renders dialog when open is true', () => { render() expect(screen.getByText('Create List')).toBeInTheDocument() }) it('renders name, description, and source type fields', () => { render() expect(screen.getByLabelText(/name/i)).toBeInTheDocument() expect(screen.getByLabelText(/description/i)).toBeInTheDocument() expect(screen.getByLabelText(/source type/i)).toBeInTheDocument() }) it('renders default source type options', () => { render() expect(screen.getByRole('option', { name: 'Static' })).toBeInTheDocument() expect(screen.getByRole('option', { name: 'Funnel' })).toBeInTheDocument() expect(screen.getByRole('option', { name: 'Query' })).toBeInTheDocument() }) it('renders custom source types when provided', () => { render( ) expect(screen.getByRole('option', { name: 'CSV Upload' })).toBeInTheDocument() expect(screen.queryByRole('option', { name: 'Static' })).not.toBeInTheDocument() }) it('Create button is disabled when name is empty', () => { render() expect(screen.getByRole('button', { name: /^create$/i })).toBeDisabled() }) it('Create button is enabled after entering a name', () => { render() fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'New List' } }) expect(screen.getByRole('button', { name: /^create$/i })).toBeEnabled() }) it('calls onSubmit with correct data', () => { const onSubmit = jest.fn() render() fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'Sales Leads' } }) fireEvent.change(screen.getByLabelText(/description/i), { target: { value: 'Top prospects' } }) fireEvent.click(screen.getByRole('button', { name: /^create$/i })) expect(onSubmit).toHaveBeenCalledWith({ name: 'Sales Leads', description: 'Top prospects', sourceType: 'static', }) }) it('trims name and description on submit', () => { const onSubmit = jest.fn() render() fireEvent.change(screen.getByLabelText(/name/i), { target: { value: ' Trimmed ' } }) fireEvent.click(screen.getByRole('button', { name: /^create$/i })) expect(onSubmit).toHaveBeenCalledWith( expect.objectContaining({ name: 'Trimmed' }) ) }) it('does not call onSubmit when name is only whitespace', () => { const onSubmit = jest.fn() render() fireEvent.change(screen.getByLabelText(/name/i), { target: { value: ' ' } }) fireEvent.submit(screen.getByLabelText(/name/i).closest('form')!) expect(onSubmit).not.toHaveBeenCalled() }) it('calls onClose when Cancel is clicked', () => { const onClose = jest.fn() render() fireEvent.click(screen.getByRole('button', { name: /cancel/i })) expect(onClose).toHaveBeenCalledTimes(1) }) it('calls onClose when backdrop is clicked', () => { const onClose = jest.fn() const { container } = render() const backdrop = container.querySelector('[aria-hidden="true"]') fireEvent.click(backdrop!) expect(onClose).toHaveBeenCalledTimes(1) }) it('does not call onClose when backdrop clicked during submit', () => { const onClose = jest.fn() const { container } = render( ) const backdrop = container.querySelector('[aria-hidden="true"]') fireEvent.click(backdrop!) expect(onClose).not.toHaveBeenCalled() }) it('shows Creating... and disables fields when isSubmitting', () => { render() expect(screen.getByText('Creating...')).toBeInTheDocument() expect(screen.getByLabelText(/name/i)).toBeDisabled() expect(screen.getByLabelText(/description/i)).toBeDisabled() }) it('resets fields when dialog reopens', () => { const { rerender } = render() rerender() expect(screen.getByLabelText(/name/i)).toHaveValue('') expect(screen.getByLabelText(/description/i)).toHaveValue('') }) it('submits the selected source type', () => { const onSubmit = jest.fn() render() fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'My List' } }) fireEvent.change(screen.getByLabelText(/source type/i), { target: { value: 'funnel' } }) fireEvent.click(screen.getByRole('button', { name: /^create$/i })) expect(onSubmit).toHaveBeenCalledWith( expect.objectContaining({ sourceType: 'funnel' }) ) }) })