diff --git a/apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx b/apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx new file mode 100644 index 0000000..0f1f445 --- /dev/null +++ b/apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx @@ -0,0 +1,204 @@ +import { cleanup, render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +// Mock next-intl — echo keys as short strings +vi.mock('next-intl', () => ({ + useTranslations: (ns: string) => (key: string, params?: Record) => { + const map: Record> = { + dkvFleet: { + emptyVehicles: 'Keine Fahrzeuge hinterlegt', + emptyVehiclesBody: 'Füge Fahrzeuge manuell hinzu.', + vehicleCount: `${params?.count ?? 0} Fahrzeuge`, + addVehicle: 'Fahrzeug hinzufügen', + editVehicle: 'Fahrzeug bearbeiten', + deleteVehicle: 'Fahrzeug löschen', + deleteVehicleBody: `Fahrzeug ${params?.plate ?? ''} wird gelöscht.`, + importCsv: 'CSV importieren', + 'col.licensePlate': 'Kennzeichen', + 'col.brand': 'Marke', + 'col.model': 'Modell', + 'col.driver': 'Fahrer', + 'col.actions': 'Aktionen', + 'form.cancelDialog': 'Abbrechen', + 'form.save': 'Speichern', + 'form.cancel': 'Bearbeitung abbrechen', + }, + }; + // Support dot-notation keys + const nsMap = map[ns] ?? {}; + return nsMap[key] ?? key; + }, +})); + +// Mock @/lib/dkv-api +const mockFetchVehicles = vi.fn(); +const mockCreateVehicle = vi.fn(); +const mockUpdateVehicle = vi.fn(); +const mockDeleteVehicle = vi.fn(); +const mockImportVehiclesCsv = vi.fn(); + +vi.mock('@/lib/dkv-api', () => ({ + fetchVehicles: (...args: unknown[]) => mockFetchVehicles(...args), + createVehicle: (...args: unknown[]) => mockCreateVehicle(...args), + updateVehicle: (...args: unknown[]) => mockUpdateVehicle(...args), + deleteVehicle: (...args: unknown[]) => mockDeleteVehicle(...args), + importVehiclesCsv: (...args: unknown[]) => mockImportVehiclesCsv(...args), +})); + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('VehicleTable', () => { + it('renders a list of two mocked vehicles (Kennzeichen, Marke, Modell, Fahrer)', async () => { + mockFetchVehicles.mockResolvedValue([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Hans Müller' }, + { id: 'v2', kennzeichen: 'B-CD 456', marke: 'BMW', modell: '3er', fahrer: 'Anna Schmidt' }, + ]); + + const { VehicleTable } = await import( + '@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('M-AB 123')).toBeInTheDocument(); + }); + + expect(screen.getByText('B-CD 456')).toBeInTheDocument(); + expect(screen.getByText('VW')).toBeInTheDocument(); + expect(screen.getByText('BMW')).toBeInTheDocument(); + expect(screen.getByText('Hans Müller')).toBeInTheDocument(); + expect(screen.getByText('Anna Schmidt')).toBeInTheDocument(); + }); + + it('shows the empty-state heading when fetchVehicles resolves []', async () => { + mockFetchVehicles.mockResolvedValue([]); + + const { VehicleTable } = await import( + '@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('Keine Fahrzeuge hinterlegt')).toBeInTheDocument(); + }); + }); + + it('clicking trash icon opens confirm dialog; confirming calls deleteVehicle with vehicle id', async () => { + mockFetchVehicles.mockResolvedValue([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Hans Müller' }, + ]); + mockDeleteVehicle.mockResolvedValue(undefined); + mockFetchVehicles.mockResolvedValueOnce([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Hans Müller' }, + ]); + + const { VehicleTable } = await import( + '@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('M-AB 123')).toBeInTheDocument(); + }); + + // Click the delete (trash) icon button + const deleteBtn = screen.getByRole('button', { name: 'Fahrzeug löschen' }); + fireEvent.click(deleteBtn); + + // Confirm dialog should appear + await waitFor(() => { + expect(screen.getByText(/wird gelöscht/i)).toBeInTheDocument(); + }); + + // Confirm the deletion + const confirmBtn = screen.getByRole('button', { name: 'Löschen' }); + fireEvent.click(confirmBtn); + + await waitFor(() => { + expect(mockDeleteVehicle).toHaveBeenCalledWith('v1'); + }); + }); + + it('clicking pencil icon switches row to edit mode; saving calls updateVehicle', async () => { + mockFetchVehicles.mockResolvedValue([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Hans Müller' }, + ]); + mockUpdateVehicle.mockResolvedValue({ + id: 'v1', + kennzeichen: 'M-AB 123', + marke: 'VW', + modell: 'Golf', + fahrer: 'Max Mustermann', + }); + // fetchVehicles called again after update + mockFetchVehicles.mockResolvedValue([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Max Mustermann' }, + ]); + + const { VehicleTable } = await import( + '@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('Hans Müller')).toBeInTheDocument(); + }); + + // Click edit (pencil) icon + const editBtn = screen.getByRole('button', { name: 'Fahrzeug bearbeiten' }); + fireEvent.click(editBtn); + + // Row should now be in edit mode — find the Fahrer input + await waitFor(() => { + const fahrerInputs = screen.getAllByRole('textbox'); + expect(fahrerInputs.length).toBeGreaterThan(0); + }); + + // Change the Fahrer field + const inputs = screen.getAllByRole('textbox'); + // Fahrer is the 4th input (Kennzeichen, Marke, Modell, Fahrer) + const fahrerInput = inputs.find((i) => (i as HTMLInputElement).value === 'Hans Müller'); + expect(fahrerInput).toBeTruthy(); + fireEvent.change(fahrerInput!, { target: { value: 'Max Mustermann' } }); + + // Click save (checkmark) button + const saveRowBtn = screen.getByRole('button', { name: 'Änderungen speichern' }); + fireEvent.click(saveRowBtn); + + await waitFor(() => { + expect(mockUpdateVehicle).toHaveBeenCalledWith('v1', expect.objectContaining({ + fahrer: 'Max Mustermann', + })); + }); + }); + + it('action buttons carry the UI-SPEC aria-labels', async () => { + mockFetchVehicles.mockResolvedValue([ + { id: 'v1', kennzeichen: 'M-AB 123', marke: 'VW', modell: 'Golf', fahrer: 'Hans Müller' }, + ]); + + const { VehicleTable } = await import( + '@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('M-AB 123')).toBeInTheDocument(); + }); + + // View mode: pencil + trash + expect(screen.getByRole('button', { name: 'Fahrzeug bearbeiten' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Fahrzeug löschen' })).toBeInTheDocument(); + + // Switch to edit mode + fireEvent.click(screen.getByRole('button', { name: 'Fahrzeug bearbeiten' })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: 'Änderungen speichern' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Bearbeitung abbrechen' })).toBeInTheDocument(); + }); + }); +});