88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { TaskDetailModal } from '../TaskDetailModal';
|
|
import { api } from '../../../lib/api';
|
|
import { Thought } from '../../../lib/types';
|
|
|
|
const task: Thought = {
|
|
id: 'th_1',
|
|
userId: 'usr_1',
|
|
content: 'Fenster putzen',
|
|
tags: [],
|
|
priority: 'normal',
|
|
source: 'manual',
|
|
status: 'converted',
|
|
isCompleted: false,
|
|
kanbanStatus: 'todo',
|
|
dueDate: '2026-10-30T14:30',
|
|
createdAt: '2026-10-29T08:00:00Z',
|
|
};
|
|
const columns = [
|
|
{ id: 'todo', name: 'Zu erledigen' },
|
|
{ id: 'done', name: 'Erledigt', isDone: true },
|
|
];
|
|
const noop = vi.fn(async () => {});
|
|
|
|
const props = (over: Partial<React.ComponentProps<typeof TaskDetailModal>> = {}) => ({
|
|
task,
|
|
isOpen: true,
|
|
onClose: vi.fn(),
|
|
activeColumns: columns,
|
|
taskLists: [],
|
|
onUpdateTitle: noop,
|
|
onUpdateStatus: noop,
|
|
onUpdatePriority: noop,
|
|
onUpdateDueDate: vi.fn(async () => {}),
|
|
onUpdateTaskList: noop,
|
|
onUpdateNotes: noop,
|
|
onToggleSubtask: noop,
|
|
onAddSubtask: noop,
|
|
onDeleteSubtask: noop,
|
|
onConvertToDoc: vi.fn(),
|
|
onDeleteTask: vi.fn(),
|
|
...over,
|
|
});
|
|
|
|
describe('TaskDetailModal', () => {
|
|
it('opens and closes without breaking the hook order', () => {
|
|
const { rerender, container } = render(<TaskDetailModal {...props({ isOpen: false })} />);
|
|
expect(container.firstChild).toBeNull();
|
|
rerender(<TaskDetailModal {...props()} />);
|
|
expect(screen.getByText('Aufgabe bearbeiten')).toBeTruthy();
|
|
rerender(<TaskDetailModal {...props({ isOpen: false })} />);
|
|
rerender(<TaskDetailModal {...props()} />);
|
|
expect(screen.getByText('Aufgabe bearbeiten')).toBeTruthy();
|
|
});
|
|
|
|
it('shows date and time separately and removes only the time', () => {
|
|
const onUpdateDueDate = vi.fn(async () => {});
|
|
render(<TaskDetailModal {...props({ onUpdateDueDate })} />);
|
|
expect((screen.getByLabelText('Fälligkeitsdatum') as HTMLInputElement).value).toBe('2026-10-30');
|
|
expect((screen.getByLabelText('Uhrzeit (optional)') as HTMLInputElement).value).toBe('14:30');
|
|
fireEvent.click(screen.getByTitle(/Uhrzeit entfernen/));
|
|
expect(onUpdateDueDate).toHaveBeenCalledWith(task, '2026-10-30');
|
|
});
|
|
|
|
it('is view-only for tasks in lists shared with viewer rights', () => {
|
|
render(<TaskDetailModal {...props({ readOnly: true })} />);
|
|
expect(screen.getByText('Aufgabe ansehen')).toBeTruthy();
|
|
expect((screen.getByLabelText('Fälligkeitsdatum') as HTMLInputElement).closest('fieldset')?.disabled).toBe(true);
|
|
});
|
|
|
|
it('toggles watching a task via POST and DELETE /tasks/{id}/watch', async () => {
|
|
const watchSpy = vi.spyOn(api, 'watchTask').mockResolvedValue({ watching: true });
|
|
const unwatchSpy = vi.spyOn(api, 'unwatchTask').mockResolvedValue({ watching: false });
|
|
|
|
render(<TaskDetailModal {...props({ task: { ...task, isWatching: false } })} />);
|
|
const btn = screen.getByRole('button', { name: 'Beobachten' });
|
|
fireEvent.click(btn);
|
|
|
|
await waitFor(() => expect(watchSpy).toHaveBeenCalledWith('th_1'));
|
|
expect(screen.getByRole('button', { name: 'Nicht mehr beobachten' })).toBeTruthy();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Nicht mehr beobachten' }));
|
|
await waitFor(() => expect(unwatchSpy).toHaveBeenCalledWith('th_1'));
|
|
expect(screen.getByRole('button', { name: 'Beobachten' })).toBeTruthy();
|
|
});
|
|
});
|