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