* Initial migration * Cleanup and create migration scripts * Introduce test suite * Fix test issues * Correct CORS issue and update paths * Update README
413 lines
No EOL
11 KiB
TypeScript
413 lines
No EOL
11 KiB
TypeScript
import React from 'react';
|
|
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
import TaskList from '../../components/Task/TaskList';
|
|
import TaskModal from '../../components/Task/TaskModal';
|
|
import { Task } from '../../entities/Task';
|
|
import { Project } from '../../entities/Project';
|
|
|
|
// Mock data
|
|
const mockTasks: Task[] = [
|
|
{
|
|
id: 1,
|
|
name: 'Complete project setup',
|
|
status: 'not_started',
|
|
priority: 'high',
|
|
created_at: '2024-01-01T00:00:00.000Z'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Write documentation',
|
|
status: 'in_progress',
|
|
priority: 'medium',
|
|
due_date: '2024-12-25',
|
|
created_at: '2024-01-02T00:00:00.000Z'
|
|
}
|
|
];
|
|
|
|
const mockProjects: Project[] = [
|
|
{
|
|
id: 1,
|
|
name: 'Test Project',
|
|
active: true
|
|
}
|
|
];
|
|
|
|
// Integration test wrapper component
|
|
const TaskWorkflowWrapper: React.FC<{
|
|
tasks: Task[];
|
|
onTaskUpdate: (task: Task) => void;
|
|
onTaskDelete: (taskId: number) => void;
|
|
}> = ({ tasks, onTaskUpdate, onTaskDelete }) => {
|
|
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
|
const [selectedTask, setSelectedTask] = React.useState<Task | null>(null);
|
|
|
|
const handleTaskClick = (task: Task) => {
|
|
setSelectedTask(task);
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleModalSave = (updatedTask: Task) => {
|
|
onTaskUpdate(updatedTask);
|
|
setIsModalOpen(false);
|
|
};
|
|
|
|
const handleModalClose = () => {
|
|
setIsModalOpen(false);
|
|
setSelectedTask(null);
|
|
};
|
|
|
|
const handleModalDelete = (taskId: number) => {
|
|
onTaskDelete(taskId);
|
|
setIsModalOpen(false);
|
|
setSelectedTask(null);
|
|
};
|
|
|
|
const CustomTaskList: React.FC<any> = (props) => (
|
|
<div>
|
|
{props.tasks.map((task: Task) => (
|
|
<div key={task.id} onClick={() => handleTaskClick(task)} style={{ cursor: 'pointer', padding: '10px', border: '1px solid #ccc', margin: '5px' }}>
|
|
<div>{task.name}</div>
|
|
<div>Status: {task.status}</div>
|
|
<div>Priority: {task.priority}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<BrowserRouter>
|
|
<div>
|
|
<CustomTaskList {...{ tasks, onTaskUpdate, onTaskDelete }} />
|
|
{isModalOpen && selectedTask && (
|
|
<TaskModal
|
|
isOpen={isModalOpen}
|
|
onClose={handleModalClose}
|
|
task={selectedTask}
|
|
onSave={handleModalSave}
|
|
onDelete={handleModalDelete}
|
|
projects={mockProjects}
|
|
onCreateProject={async (name: string) => ({ id: 999, name, active: true })}
|
|
/>
|
|
)}
|
|
</div>
|
|
</BrowserRouter>
|
|
);
|
|
};
|
|
|
|
describe('Task Workflows Integration Tests', () => {
|
|
let mockOnTaskUpdate: jest.Mock;
|
|
let mockOnTaskDelete: jest.Mock;
|
|
|
|
beforeEach(() => {
|
|
mockOnTaskUpdate = jest.fn();
|
|
mockOnTaskDelete = jest.fn();
|
|
jest.clearAllMocks();
|
|
|
|
// Mock fetch for tags service
|
|
(global.fetch as jest.Mock).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ tags: [] })
|
|
});
|
|
});
|
|
|
|
it('completes full task lifecycle workflow', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// 1. View tasks in list
|
|
expect(screen.getByText('Complete project setup')).toBeInTheDocument();
|
|
expect(screen.getByText('Write documentation')).toBeInTheDocument();
|
|
|
|
// 2. Click on a task to open modal
|
|
const firstTask = screen.getByText('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.click(firstTask);
|
|
});
|
|
|
|
// 3. Modal should open with task details
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('Complete project setup')).toBeInTheDocument();
|
|
});
|
|
|
|
// 4. Edit task name
|
|
const nameInput = screen.getByDisplayValue('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.clear(nameInput);
|
|
await user.type(nameInput, 'Updated project setup');
|
|
});
|
|
|
|
// 5. Save changes
|
|
const saveButton = screen.getByText(/save/i);
|
|
|
|
await act(async () => {
|
|
await user.click(saveButton);
|
|
});
|
|
|
|
// 6. Verify onTaskUpdate was called
|
|
expect(mockOnTaskUpdate).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
id: 1,
|
|
name: 'Updated project setup'
|
|
})
|
|
);
|
|
});
|
|
|
|
it('handles task deletion workflow', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// 1. Click on a task to open modal
|
|
const firstTask = screen.getByText('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.click(firstTask);
|
|
});
|
|
|
|
// 2. Click delete button
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/delete/i)).toBeInTheDocument();
|
|
});
|
|
|
|
const deleteButton = screen.getByText(/delete/i);
|
|
|
|
await act(async () => {
|
|
await user.click(deleteButton);
|
|
});
|
|
|
|
// 3. Confirm deletion
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/are you sure/i)).toBeInTheDocument();
|
|
});
|
|
|
|
const confirmButton = screen.getByText(/confirm/i);
|
|
|
|
await act(async () => {
|
|
await user.click(confirmButton);
|
|
});
|
|
|
|
// 4. Verify onTaskDelete was called
|
|
expect(mockOnTaskDelete).toHaveBeenCalledWith(1);
|
|
});
|
|
|
|
it('handles task status and priority updates', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Open task modal
|
|
const secondTask = screen.getByText('Write documentation');
|
|
|
|
await act(async () => {
|
|
await user.click(secondTask);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('Write documentation')).toBeInTheDocument();
|
|
});
|
|
|
|
// Change status and priority using dropdowns
|
|
const statusDropdown = screen.getByText(/status/i);
|
|
expect(statusDropdown).toBeInTheDocument();
|
|
|
|
const priorityDropdown = screen.getByText(/priority/i);
|
|
expect(priorityDropdown).toBeInTheDocument();
|
|
|
|
// Save changes
|
|
const saveButton = screen.getByText(/save/i);
|
|
|
|
await act(async () => {
|
|
await user.click(saveButton);
|
|
});
|
|
|
|
// Verify update was called
|
|
expect(mockOnTaskUpdate).toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles due date modifications', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Open task with due date
|
|
const taskWithDueDate = screen.getByText('Write documentation');
|
|
|
|
await act(async () => {
|
|
await user.click(taskWithDueDate);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('2024-12-25')).toBeInTheDocument();
|
|
});
|
|
|
|
// Update due date
|
|
const dueDateInput = screen.getByDisplayValue('2024-12-25');
|
|
|
|
await act(async () => {
|
|
await user.clear(dueDateInput);
|
|
await user.type(dueDateInput, '2024-12-31');
|
|
});
|
|
|
|
// Save changes
|
|
const saveButton = screen.getByText(/save/i);
|
|
|
|
await act(async () => {
|
|
await user.click(saveButton);
|
|
});
|
|
|
|
// Verify update with new due date
|
|
expect(mockOnTaskUpdate).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
id: 2,
|
|
due_date: '2024-12-31'
|
|
})
|
|
);
|
|
});
|
|
|
|
it('handles modal cancellation without saving', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Open task modal
|
|
const firstTask = screen.getByText('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.click(firstTask);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('Complete project setup')).toBeInTheDocument();
|
|
});
|
|
|
|
// Make changes
|
|
const nameInput = screen.getByDisplayValue('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.clear(nameInput);
|
|
await user.type(nameInput, 'Changed but not saved');
|
|
});
|
|
|
|
// Cancel instead of saving
|
|
const cancelButton = screen.getByText(/cancel/i);
|
|
|
|
await act(async () => {
|
|
await user.click(cancelButton);
|
|
});
|
|
|
|
// Verify no update was called
|
|
expect(mockOnTaskUpdate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles empty task list state', () => {
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={[]}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Should render empty container
|
|
expect(screen.queryByText('Complete project setup')).not.toBeInTheDocument();
|
|
expect(screen.queryByText('Write documentation')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('maintains task selection state during editing', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Open first task
|
|
const firstTask = screen.getByText('Complete project setup');
|
|
|
|
await act(async () => {
|
|
await user.click(firstTask);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('Complete project setup')).toBeInTheDocument();
|
|
});
|
|
|
|
// Verify correct task is loaded
|
|
expect(screen.getByDisplayValue('Complete project setup')).toBeInTheDocument();
|
|
expect(screen.queryByDisplayValue('Write documentation')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('handles multiple rapid task interactions', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<TaskWorkflowWrapper
|
|
tasks={mockTasks}
|
|
onTaskUpdate={mockOnTaskUpdate}
|
|
onTaskDelete={mockOnTaskDelete}
|
|
/>
|
|
);
|
|
|
|
// Rapidly interact with tasks
|
|
const firstTask = screen.getByText('Complete project setup');
|
|
const secondTask = screen.getByText('Write documentation');
|
|
|
|
// Click first task multiple times
|
|
await act(async () => {
|
|
await user.click(firstTask);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByDisplayValue('Complete project setup')).toBeInTheDocument();
|
|
});
|
|
|
|
// Close modal
|
|
const cancelButton = screen.getByText(/cancel/i);
|
|
await act(async () => {
|
|
await user.click(cancelButton);
|
|
});
|
|
|
|
// Should close modal properly
|
|
await waitFor(() => {
|
|
expect(screen.queryByDisplayValue('Complete project setup')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
}); |