T08b: Frontend Calendar UI (month view, kanban, ICS, resources, sharing)
- 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
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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' }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user