import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; import { ContactList } from '../contact-list'; import type { ContactCard } from '@/lib/jmap/types'; function makeContact(overrides: Partial & { id: string }): ContactCard { return { addressBookIds: {}, ...overrides, }; } const alice = makeContact({ id: '1', name: { components: [{ kind: 'given', value: 'Alice' }, { kind: 'surname', value: 'Smith' }], isOrdered: true }, emails: { e0: { address: 'alice@example.com' } }, }); const bob = makeContact({ id: '2', name: { components: [{ kind: 'given', value: 'Bob' }, { kind: 'surname', value: 'Jones' }], isOrdered: true }, emails: { e0: { address: 'bob@example.com' } }, }); const group = makeContact({ id: '3', kind: 'group', name: { components: [{ kind: 'given', value: 'Team' }], isOrdered: true }, members: { '1': true }, }); const defaultProps = { contacts: [alice, bob], selectedContactId: null, searchQuery: '', onSearchChange: vi.fn(), onSelectContact: vi.fn(), onCreateNew: vi.fn(), supportsSync: true, selectedContactIds: new Set(), onToggleSelection: vi.fn(), onSelectAll: vi.fn(), onClearSelection: vi.fn(), onBulkDelete: vi.fn(), onBulkAddToGroup: vi.fn(), onBulkExport: vi.fn(), groups: [], }; describe('ContactList', () => { it('renders contact names', () => { render(); expect(screen.getByText('Alice Smith')).toBeInTheDocument(); expect(screen.getByText('Bob Jones')).toBeInTheDocument(); }); it('filters contacts by search query', () => { render(); expect(screen.getByText('Alice Smith')).toBeInTheDocument(); expect(screen.queryByText('Bob Jones')).not.toBeInTheDocument(); }); it('shows empty state when no contacts match', () => { render(); expect(screen.getByText('empty_state')).toBeInTheDocument(); }); it('shows search empty state when search has no results', () => { render(); expect(screen.getByText('empty_search')).toBeInTheDocument(); }); it('shows local mode banner when supportsSync is false', () => { render(); expect(screen.getByText('local_mode')).toBeInTheDocument(); }); it('hides local mode banner when supportsSync is true', () => { render(); expect(screen.queryByText('local_mode')).not.toBeInTheDocument(); }); it('calls onCreateNew when create button is clicked', () => { const onCreateNew = vi.fn(); render(); fireEvent.click(screen.getByText('create_new')); expect(onCreateNew).toHaveBeenCalledOnce(); }); it('shows bulk action bar when contacts are selected', () => { render(); expect(screen.getByText('bulk.delete')).toBeInTheDocument(); expect(screen.getByText('bulk.export')).toBeInTheDocument(); }); it('excludes groups from the list', () => { render(); expect(screen.getByText('Alice Smith')).toBeInTheDocument(); expect(screen.queryByText('Team')).not.toBeInTheDocument(); }); });