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' })
)
})
})