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);
|
|||
|
|
});
|
|||
|
|
});
|