import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { ToastProvider } from '@/components/ui/Toast'; import { I18nProvider } from '@/lib/i18n'; import { VehicleList } from '@/components/vehicles/VehicleList'; import { VehicleForm } from '@/components/vehicles/VehicleForm'; import { VehicleDetail } from '@/components/vehicles/VehicleDetail'; import { MobileDeStatus } from '@/components/vehicles/MobileDeStatus'; // Mock next/navigation vi.mock('next/navigation', () => ({ useRouter: () => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn(), refresh: vi.fn(), }), redirect: vi.fn(), usePathname: () => '/de/fahrzeuge', useSearchParams: () => new URLSearchParams(), })); // Mock the vehicles API module vi.mock('@/lib/vehicles', () => ({ listVehicles: vi.fn(), getVehicle: vi.fn(), createVehicle: vi.fn(), updateVehicle: vi.fn(), deleteVehicle: vi.fn(), pushToMobileDe: vi.fn(), getMobileDeStatus: vi.fn(), })); import { listVehicles as mockListVehicles, getVehicle as mockGetVehicle, createVehicle as mockCreateVehicle, deleteVehicle as mockDeleteVehicle, pushToMobileDe as mockPushToMobileDe, getMobileDeStatus as mockGetMobileDeStatus, } from '@/lib/vehicles'; const mockVehicle = { id: 'vehicle-123', make: 'Mercedes-Benz', model: 'Actros', fin: 'WDB9066351L123456', year: 2020, first_registration: '2020-03-15', power_kw: 300, power_hp: 408, fuel_type: 'Diesel', transmission: 'Manual', color: 'White', condition: 'used', location: 'Berlin', availability: 'available', price: 45000.00, vehicle_type: 'lkw', lkw_type: 'sattelzugmaschine', machine_type: null, body_type: null, operating_hours: null, operating_hours_unit: null, mileage_km: 120000, description: 'Well maintained truck', created_at: '2025-01-01T12:00:00Z', updated_at: '2025-01-01T12:00:00Z', deleted_at: null, }; function renderWithProviders(ui: React.ReactElement) { return render( {ui} ); } describe('VehicleList', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders the vehicle list with filters and table', async () => { (mockListVehicles as any).mockResolvedValue({ items: [mockVehicle], total: 1, page: 1, page_size: 20, }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('vehicle-list')).toBeInTheDocument(); }); expect(screen.getByTestId('vehicle-filters')).toBeInTheDocument(); expect(screen.getByTestId('filter-search')).toBeInTheDocument(); expect(screen.getByTestId('filter-type')).toBeInTheDocument(); expect(screen.getByTestId('filter-availability')).toBeInTheDocument(); expect(screen.getByTestId('filter-sort')).toBeInTheDocument(); }); it('displays vehicles in the table after loading', async () => { (mockListVehicles as any).mockResolvedValue({ items: [mockVehicle], total: 1, page: 1, page_size: 20, }); renderWithProviders(); await waitFor(() => { expect(screen.getByText('Mercedes-Benz')).toBeInTheDocument(); expect(screen.getByText('Actros')).toBeInTheDocument(); expect(screen.getByText('WDB9066351L123456')).toBeInTheDocument(); }); }); it('shows pagination when total > page_size', async () => { (mockListVehicles as any).mockResolvedValue({ items: [mockVehicle], total: 40, page: 1, page_size: 20, }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('vehicle-pagination')).toBeInTheDocument(); expect(screen.getByText(/Page 1 of 2/)).toBeInTheDocument(); }); }); it('shows error message on API failure', async () => { (mockListVehicles as any).mockRejectedValue({ error: { code: 'UNKNOWN', message: 'Failed to load' }, }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('vehicle-error')).toBeInTheDocument(); expect(screen.getByText('Failed to load')).toBeInTheDocument(); }); }); it('calls listVehicles with type filter when changed', async () => { (mockListVehicles as any).mockResolvedValue({ items: [], total: 0, page: 1, page_size: 20, }); renderWithProviders(); await waitFor(() => { expect(mockListVehicles).toHaveBeenCalled(); }); const typeSelect = screen.getByTestId('filter-type'); fireEvent.change(typeSelect, { target: { value: 'lkw' } }); await waitFor(() => { expect(mockListVehicles).toHaveBeenCalledWith( expect.objectContaining({ type: 'lkw', page: 1 }) ); }); }); }); describe('VehicleForm', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders the form with all required fields', () => { renderWithProviders(); expect(screen.getByTestId('vehicle-form')).toBeInTheDocument(); expect(screen.getByTestId('input-make')).toBeInTheDocument(); expect(screen.getByTestId('input-model')).toBeInTheDocument(); expect(screen.getByTestId('input-fin')).toBeInTheDocument(); expect(screen.getByTestId('input-price')).toBeInTheDocument(); expect(screen.getByTestId('input-vehicle_type')).toBeInTheDocument(); expect(screen.getByTestId('submit-button')).toBeInTheDocument(); }); it('shows validation errors for empty required fields', async () => { renderWithProviders(); const submitButton = screen.getByTestId('submit-button'); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('Make is required')).toBeInTheDocument(); expect(screen.getByText('Model is required')).toBeInTheDocument(); expect(screen.getByText('FIN is required')).toBeInTheDocument(); }); }); it('shows error for FIN not 17 characters', async () => { renderWithProviders(); const finInput = screen.getByTestId('input-fin'); fireEvent.change(finInput, { target: { value: 'SHORT' } }); const submitButton = screen.getByTestId('submit-button'); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText('FIN must be exactly 17 characters')).toBeInTheDocument(); }); }); it('calls createVehicle on submit with valid data', async () => { (mockCreateVehicle as any).mockResolvedValue(mockVehicle); renderWithProviders(); fireEvent.change(screen.getByTestId('input-make'), { target: { value: 'Volvo' } }); fireEvent.change(screen.getByTestId('input-model'), { target: { value: 'FH16' } }); fireEvent.change(screen.getByTestId('input-fin'), { target: { value: 'WDB9066351L123456' } }); fireEvent.change(screen.getByTestId('input-price'), { target: { value: '85000' } }); fireEvent.click(screen.getByTestId('submit-button')); await waitFor(() => { expect(mockCreateVehicle).toHaveBeenCalledWith( expect.objectContaining({ make: 'Volvo', model: 'FH16', fin: 'WDB9066351L123456', price: 85000, }) ); }); }); }); describe('VehicleDetail', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders vehicle details after loading', async () => { (mockGetVehicle as any).mockResolvedValue(mockVehicle); (mockGetMobileDeStatus as any).mockResolvedValue({ synced: false, sync_status: 'pending', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('vehicle-detail')).toBeInTheDocument(); }); expect(screen.getByTestId('vehicle-detail-title')).toHaveTextContent('Mercedes-Benz Actros'); const fieldsContainer = screen.getByTestId('vehicle-detail-fields'); expect(fieldsContainer).toHaveTextContent('WDB9066351L123456'); expect(fieldsContainer).toHaveTextContent('lkw'); expect(fieldsContainer).toHaveTextContent('available'); }); it('shows mobile.de status section', async () => { (mockGetVehicle as any).mockResolvedValue(mockVehicle); (mockGetMobileDeStatus as any).mockResolvedValue({ synced: true, ad_id: 'ad-123', synced_at: '2025-01-01T12:00:00Z', sync_status: 'synced', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('mobile-de-status')).toBeInTheDocument(); }); expect(screen.getByTestId('mobile-de-sync-status')).toHaveTextContent('Synced'); }); it('shows error message on API failure', async () => { (mockGetVehicle as any).mockRejectedValue({ error: { code: 'VEHICLE_NOT_FOUND', message: 'Vehicle not found' }, }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('vehicle-detail-error')).toBeInTheDocument(); }); }); }); describe('MobileDeStatus', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders with pending status when no listing exists', async () => { (mockGetMobileDeStatus as any).mockResolvedValue({ synced: false, sync_status: 'pending', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('mobile-de-sync-status')).toHaveTextContent('Pending'); }); expect(screen.getByTestId('mobile-de-synced')).toHaveTextContent('No'); }); it('shows synced status after successful push', async () => { (mockGetMobileDeStatus as any).mockResolvedValue({ synced: true, ad_id: 'ad-456', synced_at: '2025-06-01T10:00:00Z', sync_status: 'synced', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('mobile-de-sync-status')).toHaveTextContent('Synced'); }); expect(screen.getByTestId('mobile-de-synced')).toHaveTextContent('Yes'); expect(screen.getByText('ad-456')).toBeInTheDocument(); }); it('shows error log when sync failed', async () => { (mockGetMobileDeStatus as any).mockResolvedValue({ synced: false, sync_status: 'fehler', error_log: 'HTTP 500: Internal Server Error', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('mobile-de-sync-status')).toHaveTextContent('Error'); expect(screen.getByTestId('mobile-de-error-log')).toBeInTheDocument(); expect(screen.getByText(/HTTP 500/)).toBeInTheDocument(); }); }); it('calls pushToMobileDe when push button is clicked', async () => { (mockGetMobileDeStatus as any).mockResolvedValue({ synced: false, sync_status: 'pending', }); (mockPushToMobileDe as any).mockResolvedValue({ message: 'Push queued', vehicle_id: 'vehicle-123', }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('mobile-de-push-button')).toBeInTheDocument(); }); fireEvent.click(screen.getByTestId('mobile-de-push-button')); await waitFor(() => { expect(mockPushToMobileDe).toHaveBeenCalledWith('vehicle-123'); }); }); });