import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { ContactList } from '@/components/contacts/ContactList'; import { ContactForm } from '@/components/contacts/ContactForm'; import { validateVatIdFormat } from '@/lib/contacts'; // Mock the contacts API module vi.mock('@/lib/contacts', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, listContacts: vi.fn(), createContact: vi.fn(), updateContact: vi.fn(), }; }); import { listContacts, createContact } from '@/lib/contacts'; const mockContacts = { items: [ { id: 'test-id-1', company_name: 'Müller Transport GmbH', address_city: 'Berlin', address_country: 'DE', role: 'kaeufer', vat_id: 'DE123456789', vat_id_status: 'ungeprueft', is_private: false, contact_persons: [], }, { id: 'test-id-2', company_name: 'Van der Berg B.V.', address_city: 'Amsterdam', address_country: 'NL', role: 'verkaeufer', vat_id: 'NL123456789B01', vat_id_status: 'geprueft', is_private: false, contact_persons: [{ id: 'p1', contact_id: 'test-id-2', name: 'Jan' }], }, ], total: 2, page: 1, page_size: 20, }; describe('ContactList', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders contact list with search and filter controls', async () => { vi.mocked(listContacts).mockResolvedValue(mockContacts); render(); await waitFor(() => { expect(screen.getByTestId('contact-list')).toBeInTheDocument(); }); expect(screen.getByTestId('filter-search')).toBeInTheDocument(); expect(screen.getByTestId('filter-role')).toBeInTheDocument(); expect(screen.getByTestId('filter-eu')).toBeInTheDocument(); expect(screen.getByTestId('filter-sort')).toBeInTheDocument(); }); it('displays contacts in table after loading', async () => { vi.mocked(listContacts).mockResolvedValue(mockContacts); render(); await waitFor(() => { expect(screen.getByText('Müller Transport GmbH')).toBeInTheDocument(); }); expect(screen.getByText('Van der Berg B.V.')).toBeInTheDocument(); expect(screen.getByText('Berlin')).toBeInTheDocument(); expect(screen.getByText('Amsterdam')).toBeInTheDocument(); }); it('shows new contact button', async () => { vi.mocked(listContacts).mockResolvedValue(mockContacts); render(); await waitFor(() => { expect(screen.getByTestId('new-contact-btn')).toBeInTheDocument(); }); }); it('shows error message on API failure', async () => { vi.mocked(listContacts).mockRejectedValue({ error: { message: 'Network error' }, }); render(); await waitFor(() => { expect(screen.getByTestId('contact-error')).toBeInTheDocument(); }); expect(screen.getByText('Network error')).toBeInTheDocument(); }); it('shows loading state initially', async () => { vi.mocked(listContacts).mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve(mockContacts), 100)) ); render(); expect(screen.getByTestId('contact-loading')).toBeInTheDocument(); await waitFor(() => { expect(screen.getByText('Müller Transport GmbH')).toBeInTheDocument(); }); }); }); describe('ContactForm', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders form with all required fields', () => { render(); expect(screen.getByTestId('contact-form')).toBeInTheDocument(); expect(screen.getByTestId('input-company_name')).toBeInTheDocument(); expect(screen.getByTestId('input-role')).toBeInTheDocument(); expect(screen.getByTestId('input-address_country')).toBeInTheDocument(); expect(screen.getByTestId('input-vat_id')).toBeInTheDocument(); expect(screen.getByTestId('input-phone')).toBeInTheDocument(); expect(screen.getByTestId('input-email')).toBeInTheDocument(); expect(screen.getByTestId('submit-button')).toBeInTheDocument(); }); it('shows EU/Inland toggle', () => { render(); expect(screen.getByTestId('eu-inland-toggle')).toBeInTheDocument(); expect(screen.getByTestId('toggle-inland')).toBeInTheDocument(); expect(screen.getByTestId('toggle-eu')).toBeInTheDocument(); }); it('defaults to Inland (DE) and shows DE placeholder for VAT ID', () => { render(); const inlandRadio = screen.getByTestId('toggle-inland') as HTMLInputElement; expect(inlandRadio.checked).toBe(true); }); it('switches to EU mode and shows VAT ID hint when EU is selected', () => { render(); const euRadio = screen.getByTestId('toggle-eu'); fireEvent.click(euRadio); expect(screen.getByTestId('vat-id-hint')).toBeInTheDocument(); }); it('validates VAT ID format and shows error for invalid DE VAT', () => { render(); const vatInput = screen.getByTestId('input-vat_id') as HTMLInputElement; fireEvent.change(vatInput, { target: { value: 'DE123' } }); const submitBtn = screen.getByTestId('submit-button'); fireEvent.click(submitBtn); expect(screen.getByText(/Invalid VAT ID format/)).toBeInTheDocument(); }); it('accepts valid DE VAT ID without error', async () => { vi.mocked(createContact).mockResolvedValue({ id: 'new-id', company_name: 'Test GmbH', address_country: 'DE', role: 'kaeufer', vat_id_status: 'ungeprueft', is_private: false, contact_persons: [], }); render(); const companyInput = screen.getByTestId('input-company_name'); fireEvent.change(companyInput, { target: { value: 'Test GmbH' } }); const vatInput = screen.getByTestId('input-vat_id'); fireEvent.change(vatInput, { target: { value: 'DE123456789' } }); const submitBtn = screen.getByTestId('submit-button'); fireEvent.click(submitBtn); await waitFor(() => { expect(createContact).toHaveBeenCalled(); }); }); it('requires company name', () => { render(); const submitBtn = screen.getByTestId('submit-button'); fireEvent.click(submitBtn); expect(screen.getByText('Company name is required')).toBeInTheDocument(); }); }); describe('validateVatIdFormat', () => { it('returns null for empty VAT ID', () => { expect(validateVatIdFormat('', 'DE')).toBeNull(); }); it('returns null for valid DE VAT ID', () => { expect(validateVatIdFormat('DE123456789', 'DE')).toBeNull(); }); it('returns error for invalid DE VAT ID (too short)', () => { const result = validateVatIdFormat('DE12345678', 'DE'); expect(result).toContain('Invalid'); }); it('returns null for valid AT VAT ID', () => { expect(validateVatIdFormat('ATU12345678', 'AT')).toBeNull(); }); it('returns null for valid NL VAT ID', () => { expect(validateVatIdFormat('NL123456789B01', 'NL')).toBeNull(); }); it('returns error for non-EU country', () => { const result = validateVatIdFormat('US123456789', 'US'); expect(result).toContain('not supported'); }); it('handles lowercase input', () => { expect(validateVatIdFormat('de123456789', 'DE')).toBeNull(); }); it('handles spaces in VAT ID', () => { expect(validateVatIdFormat('DE 123 456 789', 'DE')).toBeNull(); }); });