7350739554
- 8 calendar components (MonthView, KanbanBoard, AppointmentModal, TaskDetailPanel, IcsControls, ResourceBooking, SharingSettings + API client) - 2 pages (/calendar, /calendar/kanban) + zustand store - 17 vitest tests (MonthView 5, KanbanBoard 6, AppointmentModal 6) all passing - i18n: calendar namespace in en/de (104 lines each, +exportSuccess key) - TS strict mode pass, npm run build pass
93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
import React from 'react';
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import { KanbanBoard } from '@/components/calendar/KanbanBoard';
|
|
import type { CalendarEntry } from '@/api/calendar';
|
|
|
|
function makeTask(id: string, title: string, status: CalendarEntry['status']): CalendarEntry {
|
|
return {
|
|
id,
|
|
calendar_id: 'cal-1',
|
|
entry_type: 'task',
|
|
subtype: 'normal',
|
|
title,
|
|
all_day: false,
|
|
priority: 'medium',
|
|
status,
|
|
created_by: 'u-1',
|
|
due_date: '2026-07-01T00:00:00.000Z',
|
|
};
|
|
}
|
|
|
|
const board = {
|
|
open: [makeTask('t-1', 'Recherche', 'open')],
|
|
in_progress: [makeTask('t-2', 'Implementierung', 'in_progress')],
|
|
done: [makeTask('t-3', 'Tests', 'done')],
|
|
cancelled: [],
|
|
};
|
|
|
|
describe('KanbanBoard', () => {
|
|
it('renders three columns (open / in_progress / done)', () => {
|
|
render(<KanbanBoard board={board} onMove={vi.fn()} />);
|
|
expect(screen.getByTestId('kanban-column-open')).toBeInTheDocument();
|
|
expect(screen.getByTestId('kanban-column-in_progress')).toBeInTheDocument();
|
|
expect(screen.getByTestId('kanban-column-done')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders task cards inside the correct columns', () => {
|
|
render(<KanbanBoard board={board} onMove={vi.fn()} />);
|
|
expect(screen.getByTestId('kanban-card-t-1').textContent).toContain('Recherche');
|
|
expect(screen.getByTestId('kanban-card-t-2').textContent).toContain('Implementierung');
|
|
expect(screen.getByTestId('kanban-card-t-3').textContent).toContain('Tests');
|
|
});
|
|
|
|
it('shows column counts', () => {
|
|
render(<KanbanBoard board={board} onMove={vi.fn()} />);
|
|
expect(screen.getByTestId('kanban-count-open').textContent).toBe('1');
|
|
expect(screen.getByTestId('kanban-count-in_progress').textContent).toBe('1');
|
|
expect(screen.getByTestId('kanban-count-done').textContent).toBe('1');
|
|
});
|
|
|
|
it('shows empty state when there are no tasks', () => {
|
|
render(
|
|
<KanbanBoard
|
|
board={{ open: [], in_progress: [], done: [], cancelled: [] }}
|
|
onMove={vi.fn()}
|
|
/>
|
|
);
|
|
expect(screen.getByTestId('kanban-empty')).toBeInTheDocument();
|
|
});
|
|
|
|
it('drag-and-drop into another column fires onMove with target status', () => {
|
|
const onMove = vi.fn();
|
|
render(<KanbanBoard board={board} onMove={onMove} />);
|
|
const card = screen.getByTestId('kanban-card-t-1');
|
|
const targetCol = screen.getByTestId('kanban-column-done');
|
|
const dataTransfer = {
|
|
setData: vi.fn(),
|
|
getData: (k: string) => {
|
|
if (k === 'application/x-kanban-entry') return 't-1';
|
|
if (k === 'application/x-kanban-source') return 'open';
|
|
return '';
|
|
},
|
|
types: ['application/x-kanban-entry', 'application/x-kanban-source'],
|
|
effectAllowed: '',
|
|
dropEffect: '',
|
|
};
|
|
fireEvent.dragStart(card, { dataTransfer });
|
|
fireEvent.dragOver(targetCol, { dataTransfer });
|
|
fireEvent.drop(targetCol, { dataTransfer });
|
|
expect(onMove).toHaveBeenCalledTimes(1);
|
|
const [movedEntry, target] = onMove.mock.calls[0];
|
|
expect(movedEntry.id).toBe('t-1');
|
|
expect(target).toBe('done');
|
|
});
|
|
|
|
it('clicking a card fires onSelect', () => {
|
|
const onSelect = vi.fn();
|
|
render(<KanbanBoard board={board} onMove={vi.fn()} onSelect={onSelect} />);
|
|
fireEvent.click(screen.getByTestId('kanban-card-t-2'));
|
|
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 't-2' }));
|
|
});
|
|
});
|