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