test(07-05): add failing VehicleTable tests (RED — DKV-03)
- Covers: render list, empty state, delete with confirm, inline edit + save - Mocks @/lib/dkv-api + next-intl (mirrors calendar-settings.test.tsx pattern) - Verifies all 4 UI-SPEC aria-labels on icon-only action buttons
This commit is contained in:
+204
@@ -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<string, unknown>) => {
|
||||
const map: Record<string, Record<string, string>> = {
|
||||
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(<VehicleTable />);
|
||||
|
||||
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(<VehicleTable />);
|
||||
|
||||
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(<VehicleTable />);
|
||||
|
||||
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(<VehicleTable />);
|
||||
|
||||
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(<VehicleTable />);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user