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
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import React from 'react';
|
||
import { describe, it, expect, vi } from 'vitest';
|
||
import { render, screen, fireEvent } from '@testing-library/react';
|
||
import { MonthView } from '@/components/calendar/MonthView';
|
||
import type { CalendarEntry } from '@/api/calendar';
|
||
|
||
const sampleEntries: CalendarEntry[] = [
|
||
{
|
||
id: 'a-1',
|
||
calendar_id: 'cal-1',
|
||
entry_type: 'appointment',
|
||
subtype: 'normal',
|
||
title: 'Team-Meeting',
|
||
start_at: '2026-06-15T09:00:00.000Z',
|
||
end_at: '2026-06-15T10:00:00.000Z',
|
||
all_day: false,
|
||
priority: 'medium',
|
||
status: 'open',
|
||
created_by: 'u-1',
|
||
},
|
||
{
|
||
id: 'a-2',
|
||
calendar_id: 'cal-1',
|
||
entry_type: 'appointment',
|
||
subtype: 'normal',
|
||
title: 'Kundentermin',
|
||
start_at: '2026-06-22T14:00:00.000Z',
|
||
end_at: '2026-06-22T15:00:00.000Z',
|
||
all_day: false,
|
||
priority: 'high',
|
||
status: 'open',
|
||
created_by: 'u-1',
|
||
},
|
||
];
|
||
|
||
describe('MonthView', () => {
|
||
it('renders the month grid with header', () => {
|
||
render(
|
||
<MonthView
|
||
visibleMonth={new Date(2026, 5, 1)}
|
||
entries={[]}
|
||
onCreateAt={vi.fn()}
|
||
onEditEntry={vi.fn()}
|
||
onMoveEntry={vi.fn()}
|
||
/>
|
||
);
|
||
expect(screen.getByTestId('month-view')).toBeInTheDocument();
|
||
expect(screen.getByTestId('month-view-grid')).toBeInTheDocument();
|
||
// Header includes month name and year
|
||
expect(screen.getByTestId('month-view-header').textContent).toContain('2026');
|
||
});
|
||
|
||
it('renders all 42 day cells (6 weeks × 7 days)', () => {
|
||
render(
|
||
<MonthView
|
||
visibleMonth={new Date(2026, 5, 1)}
|
||
entries={[]}
|
||
onCreateAt={vi.fn()}
|
||
onEditEntry={vi.fn()}
|
||
onMoveEntry={vi.fn()}
|
||
/>
|
||
);
|
||
const cells = screen.getAllByRole('button', { hidden: false }).filter((el) =>
|
||
el.getAttribute('data-testid')?.startsWith('month-cell-')
|
||
);
|
||
expect(cells.length).toBe(42);
|
||
});
|
||
|
||
it('renders an entry chip for each appointment and clicking it fires onEditEntry', () => {
|
||
const onEdit = vi.fn();
|
||
render(
|
||
<MonthView
|
||
visibleMonth={new Date(2026, 5, 1)}
|
||
entries={sampleEntries}
|
||
onCreateAt={vi.fn()}
|
||
onEditEntry={onEdit}
|
||
onMoveEntry={vi.fn()}
|
||
/>
|
||
);
|
||
expect(screen.getByTestId('month-entry-a-1')).toBeInTheDocument();
|
||
expect(screen.getByTestId('month-entry-a-2')).toBeInTheDocument();
|
||
fireEvent.click(screen.getByTestId('month-entry-a-1'));
|
||
expect(onEdit).toHaveBeenCalledWith(
|
||
expect.objectContaining({ id: 'a-1', title: 'Team-Meeting' })
|
||
);
|
||
});
|
||
|
||
it('clicking an empty cell fires onCreateAt with the day Date', () => {
|
||
const onCreate = vi.fn();
|
||
render(
|
||
<MonthView
|
||
visibleMonth={new Date(2026, 5, 1)}
|
||
entries={[]}
|
||
onCreateAt={onCreate}
|
||
onEditEntry={vi.fn()}
|
||
onMoveEntry={vi.fn()}
|
||
/>
|
||
);
|
||
// Pick the first cell of the grid
|
||
const cells = screen.getAllByRole('button', { hidden: false }).filter((el) =>
|
||
el.getAttribute('data-testid')?.startsWith('month-cell-')
|
||
);
|
||
fireEvent.click(cells[0]);
|
||
expect(onCreate).toHaveBeenCalledTimes(1);
|
||
const arg = onCreate.mock.calls[0][0] as Date;
|
||
expect(arg).toBeInstanceOf(Date);
|
||
});
|
||
|
||
it('drag-and-drop fires onMoveEntry with the new start time-of-day preserved', () => {
|
||
const onMove = vi.fn();
|
||
const { container } = render(
|
||
<MonthView
|
||
visibleMonth={new Date(2026, 5, 1)}
|
||
entries={sampleEntries}
|
||
onCreateAt={vi.fn()}
|
||
onEditEntry={vi.fn()}
|
||
onMoveEntry={onMove}
|
||
/>
|
||
);
|
||
const entryEl = screen.getByTestId('month-entry-a-1');
|
||
const cells = container.querySelectorAll('[data-testid^="month-cell-"]');
|
||
// Pick a cell that's clearly different from June 15
|
||
const targetCell = cells[20] as HTMLElement;
|
||
const dataTransfer = {
|
||
setData: vi.fn(),
|
||
getData: (k: string) => (k === 'application/x-calendar-entry' ? 'a-1' : ''),
|
||
types: ['application/x-calendar-entry'],
|
||
effectAllowed: '',
|
||
dropEffect: '',
|
||
};
|
||
fireEvent.dragStart(entryEl, { dataTransfer });
|
||
fireEvent.dragOver(targetCell, { dataTransfer });
|
||
fireEvent.drop(targetCell, { dataTransfer });
|
||
expect(onMove).toHaveBeenCalledTimes(1);
|
||
const [, newStart] = onMove.mock.calls[0];
|
||
expect(newStart).toBeInstanceOf(Date);
|
||
});
|
||
});
|