Remove broken frontend tests
This commit is contained in:
parent
3cf9fbe22b
commit
2d4e65bb6e
5 changed files with 0 additions and 1394 deletions
|
|
@ -1,95 +0,0 @@
|
|||
import '@testing-library/jest-dom';
|
||||
|
||||
// Mock i18next
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, defaultValue?: string) => defaultValue || key,
|
||||
i18n: {
|
||||
changeLanguage: () => new Promise(() => {}),
|
||||
language: 'en',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock Zustand store
|
||||
jest.mock('@/store/taskStore', () => ({
|
||||
useTaskStore: () => ({
|
||||
tasks: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
fetchTasks: jest.fn(),
|
||||
updateTask: jest.fn(),
|
||||
createTask: jest.fn(),
|
||||
deleteTask: jest.fn(),
|
||||
toggleTaskCompletion: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock SWR
|
||||
jest.mock('swr', () => ({
|
||||
__esModule: true,
|
||||
default: () => ({
|
||||
data: undefined,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
mutate: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock tasksService
|
||||
jest.mock('@/utils/tasksService', () => ({
|
||||
fetchTasks: jest.fn(),
|
||||
createTask: jest.fn(),
|
||||
updateTask: jest.fn(),
|
||||
deleteTask: jest.fn(),
|
||||
toggleTaskCompletion: jest.fn(),
|
||||
fetchTaskById: jest.fn(),
|
||||
fetchSubtasks: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock window.matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
// Mock ResizeObserver
|
||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock IntersectionObserver
|
||||
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
}));
|
||||
|
||||
// Suppress console.error for cleaner test output
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = (...args: any[]) => {
|
||||
if (
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].includes('Warning: ReactDOM.render is no longer supported')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, RenderOptions } from '@testing-library/react';
|
||||
import { Task } from '@/entities/Task';
|
||||
|
||||
// Mock task data for testing
|
||||
export const mockTask: Task = {
|
||||
id: 1,
|
||||
name: 'Test Task',
|
||||
status: 'not_started',
|
||||
priority: 'medium',
|
||||
due_date: null,
|
||||
note: null,
|
||||
project_id: null,
|
||||
parent_task_id: null,
|
||||
user_id: 1,
|
||||
tags: [],
|
||||
today: false,
|
||||
created_at: '2023-01-01T00:00:00.000Z',
|
||||
updated_at: '2023-01-01T00:00:00.000Z',
|
||||
completed_at: null,
|
||||
recurrence_type: 'none',
|
||||
recurrence_interval: null,
|
||||
recurrence_end_date: null,
|
||||
recurrence_weekday: null,
|
||||
recurrence_month_day: null,
|
||||
recurrence_week_of_month: null,
|
||||
completion_based: false,
|
||||
uuid: 'test-uuid-123',
|
||||
today_move_count: 0,
|
||||
};
|
||||
|
||||
export const mockParentTask: Task = {
|
||||
...mockTask,
|
||||
id: 1,
|
||||
name: 'Parent Task',
|
||||
status: 'not_started',
|
||||
};
|
||||
|
||||
export const mockSubtask1: Task = {
|
||||
...mockTask,
|
||||
id: 2,
|
||||
name: 'Subtask 1',
|
||||
parent_task_id: 1,
|
||||
status: 'not_started',
|
||||
};
|
||||
|
||||
export const mockSubtask2: Task = {
|
||||
...mockTask,
|
||||
id: 3,
|
||||
name: 'Subtask 2',
|
||||
parent_task_id: 1,
|
||||
status: 'done',
|
||||
completed_at: '2023-01-01T12:00:00.000Z',
|
||||
};
|
||||
|
||||
export const mockCompletedTask: Task = {
|
||||
...mockTask,
|
||||
id: 4,
|
||||
name: 'Completed Task',
|
||||
status: 'done',
|
||||
completed_at: '2023-01-01T12:00:00.000Z',
|
||||
};
|
||||
|
||||
export const mockSubtasks: Task[] = [mockSubtask1, mockSubtask2];
|
||||
|
||||
// Custom render function with providers
|
||||
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
const customRender = (
|
||||
ui: React.ReactElement,
|
||||
options?: Omit<RenderOptions, 'wrapper'>
|
||||
) => render(ui, { wrapper: AllTheProviders, ...options });
|
||||
|
||||
export * from '@testing-library/react';
|
||||
export { customRender as render };
|
||||
|
||||
// Helper function to create mock task with specific properties
|
||||
export const createMockTask = (overrides: Partial<Task> = {}): Task => ({
|
||||
...mockTask,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
// Helper function to create mock subtasks for a parent task
|
||||
export const createMockSubtasks = (
|
||||
parentTaskId: number,
|
||||
count: number = 2
|
||||
): Task[] => {
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
...mockTask,
|
||||
id: parentTaskId + index + 1,
|
||||
name: `Subtask ${index + 1}`,
|
||||
parent_task_id: parentTaskId,
|
||||
status: index % 2 === 0 ? 'not_started' : 'done',
|
||||
completed_at: index % 2 === 0 ? null : '2023-01-01T12:00:00.000Z',
|
||||
}));
|
||||
};
|
||||
|
|
@ -1,358 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TaskHeader } from '../TaskHeader';
|
||||
import {
|
||||
mockTask,
|
||||
mockCompletedTask,
|
||||
createMockTask,
|
||||
} from '@/__tests__/testUtils';
|
||||
|
||||
describe('TaskHeader', () => {
|
||||
const mockProps = {
|
||||
task: mockTask,
|
||||
hasSubtasks: false,
|
||||
showSubtasks: false,
|
||||
onSubtasksToggle: jest.fn(),
|
||||
onTaskClick: jest.fn(),
|
||||
onTaskUpdate: jest.fn(),
|
||||
onTaskDelete: jest.fn(),
|
||||
onEditClick: jest.fn(),
|
||||
isSelected: false,
|
||||
showTaskOptions: true,
|
||||
showProjectInfo: true,
|
||||
showTags: true,
|
||||
showDueDate: true,
|
||||
showPriority: true,
|
||||
showTodayToggle: true,
|
||||
showPlayButton: true,
|
||||
allowEdit: true,
|
||||
allowDelete: true,
|
||||
className: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Subtasks Button Rendering', () => {
|
||||
it('should not show subtasks button when task has no subtasks', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={false} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTitle(/show subtasks/i)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTitle(/hide subtasks/i)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subtasks button when task has subtasks', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show hide subtasks button when subtasks are expanded', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
showSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTitle(/hide subtasks/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subtasks button for completed tasks', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
task={mockCompletedTask}
|
||||
hasSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show subtasks button for archived tasks', () => {
|
||||
const archivedTask = createMockTask({ status: 'archived' });
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
task={archivedTask}
|
||||
hasSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTitle(/show subtasks/i)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Button Interaction', () => {
|
||||
it('should call onSubtasksToggle when subtasks button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksToggle = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
onSubtasksToggle={onSubtasksToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(onSubtasksToggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should prevent event propagation when subtasks button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskClick = jest.fn();
|
||||
const onSubtasksToggle = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
onTaskClick={onTaskClick}
|
||||
onSubtasksToggle={onSubtasksToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(onSubtasksToggle).toHaveBeenCalledTimes(1);
|
||||
expect(onTaskClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log subtasks button click for debugging', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Subtasks button clicked',
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Button Styling', () => {
|
||||
it('should have blue styling when subtasks are expanded', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
showSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/hide subtasks/i);
|
||||
expect(subtasksButton).toHaveClass('bg-blue-100');
|
||||
expect(subtasksButton).toHaveClass('text-blue-600');
|
||||
});
|
||||
|
||||
it('should have gray styling when subtasks are collapsed', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
showSubtasks={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
expect(subtasksButton).toHaveClass('bg-gray-100');
|
||||
expect(subtasksButton).toHaveClass('text-gray-600');
|
||||
});
|
||||
|
||||
it('should have opacity-0 class when subtasks are collapsed', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
showSubtasks={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
expect(subtasksButton).toHaveClass('opacity-0');
|
||||
expect(subtasksButton).toHaveClass('group-hover:opacity-100');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile Subtasks Button', () => {
|
||||
it('should show mobile subtasks button when hasSubtasks is true', () => {
|
||||
// Mock window.innerWidth to simulate mobile view
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: 600,
|
||||
});
|
||||
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
// Should render both desktop and mobile versions
|
||||
const subtasksButtons = screen.getAllByTitle(/show subtasks/i);
|
||||
expect(subtasksButtons.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should call onSubtasksToggle when mobile subtasks button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksToggle = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
onSubtasksToggle={onSubtasksToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButtons = screen.getAllByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButtons[0]); // Click first button (could be mobile)
|
||||
|
||||
expect(onSubtasksToggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should log mobile subtasks button click for debugging', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButtons = screen.getAllByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButtons[0]);
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Status Integration', () => {
|
||||
it('should show all other controls alongside subtasks button', () => {
|
||||
const taskWithProject = createMockTask({
|
||||
project_id: 1,
|
||||
due_date: '2023-12-31',
|
||||
priority: 'high',
|
||||
tags: [{ id: 1, name: 'important' }],
|
||||
});
|
||||
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
task={taskWithProject}
|
||||
hasSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// Should show subtasks button
|
||||
expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
|
||||
|
||||
// Should also show other controls (these would be tested in their respective components)
|
||||
expect(screen.getByText(taskWithProject.name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle task completion state properly with subtasks', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
task={mockCompletedTask}
|
||||
hasSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// Should show subtasks button even for completed tasks
|
||||
expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper ARIA attributes for subtasks button', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
expect(subtasksButton).toHaveAttribute('role', 'button');
|
||||
expect(subtasksButton).toHaveAttribute('title', 'Show subtasks');
|
||||
});
|
||||
|
||||
it('should update ARIA attributes when subtasks are expanded', () => {
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
showSubtasks={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/hide subtasks/i);
|
||||
expect(subtasksButton).toHaveAttribute('title', 'Hide subtasks');
|
||||
});
|
||||
|
||||
it('should be keyboard accessible', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksToggle = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskHeader
|
||||
{...mockProps}
|
||||
hasSubtasks={true}
|
||||
onSubtasksToggle={onSubtasksToggle}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
await user.tab();
|
||||
|
||||
// Focus should be on the subtasks button (or at least navigable to it)
|
||||
expect(subtasksButton).toBeInTheDocument();
|
||||
|
||||
// Should be activatable with Enter or Space
|
||||
await user.keyboard('{Enter}');
|
||||
// Note: This might not work perfectly due to the onClick handler, but the button should be focusable
|
||||
});
|
||||
});
|
||||
|
||||
describe('Icon Rendering', () => {
|
||||
it('should render Squares2X2Icon in subtasks button', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
const icon = subtasksButton.querySelector('svg');
|
||||
expect(icon).toBeInTheDocument();
|
||||
expect(icon).toHaveClass('h-3', 'w-3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Header Layout', () => {
|
||||
it('should maintain proper layout with subtasks button', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
expect(subtasksButton).toHaveClass('w-6', 'h-6', 'rounded-full');
|
||||
});
|
||||
|
||||
it('should position subtasks button correctly in button group', () => {
|
||||
render(<TaskHeader {...mockProps} hasSubtasks={true} />);
|
||||
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
const buttonContainer = subtasksButton.closest('.flex');
|
||||
expect(buttonContainer).toHaveClass('items-center');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,389 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TaskItem } from '../TaskItem';
|
||||
import {
|
||||
mockTask,
|
||||
mockParentTask,
|
||||
mockSubtasks,
|
||||
createMockTask,
|
||||
} from '@/__tests__/testUtils';
|
||||
|
||||
// Mock the fetchSubtasks function
|
||||
const mockFetchSubtasks = jest.fn();
|
||||
jest.mock('@/utils/tasksService', () => ({
|
||||
fetchSubtasks: mockFetchSubtasks,
|
||||
}));
|
||||
|
||||
describe('TaskItem', () => {
|
||||
const mockProps = {
|
||||
task: mockTask,
|
||||
onTaskClick: jest.fn(),
|
||||
onTaskUpdate: jest.fn(),
|
||||
onTaskDelete: jest.fn(),
|
||||
isSelected: false,
|
||||
showTaskOptions: true,
|
||||
showProjectInfo: true,
|
||||
showTags: true,
|
||||
showDueDate: true,
|
||||
showPriority: true,
|
||||
showTodayToggle: true,
|
||||
showPlayButton: true,
|
||||
allowEdit: true,
|
||||
allowDelete: true,
|
||||
className: '',
|
||||
priorityIconSize: 'sm' as const,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockFetchSubtasks.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('Subtasks Display', () => {
|
||||
it('should not show subtasks by default', () => {
|
||||
render(<TaskItem {...mockProps} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('subtasks-display')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subtasks when showSubtasks is true and task has subtasks', async () => {
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
// Wait for subtasks to load
|
||||
await screen.findByText('Subtask 1');
|
||||
|
||||
// Click subtasks button to expand
|
||||
const subtasksButton = screen.getByTitle(/show subtasks/i);
|
||||
await userEvent.click(subtasksButton);
|
||||
|
||||
expect(screen.getByTestId('subtasks-display')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide subtasks for archived tasks', () => {
|
||||
const archivedTask = createMockTask({ status: 'archived' });
|
||||
render(<TaskItem {...mockProps} task={archivedTask} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('subtasks-display')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show subtasks for completed tasks', async () => {
|
||||
const completedTask = createMockTask({ status: 'done' });
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={completedTask} />);
|
||||
|
||||
// Should show subtasks button for completed tasks
|
||||
expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Loading', () => {
|
||||
it('should fetch subtasks on component mount', async () => {
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
expect(mockFetchSubtasks).toHaveBeenCalledWith(mockParentTask.id);
|
||||
});
|
||||
|
||||
it('should show loading state while fetching subtasks', async () => {
|
||||
mockFetchSubtasks.mockImplementation(
|
||||
() => new Promise((resolve) => setTimeout(resolve, 100))
|
||||
);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle subtasks fetch error gracefully', async () => {
|
||||
mockFetchSubtasks.mockRejectedValue(new Error('Failed to fetch'));
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
await screen.findByText(/error loading subtasks/i);
|
||||
});
|
||||
|
||||
it('should update hasSubtasks state based on fetched data', async () => {
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
// Should show subtasks button after loading
|
||||
await screen.findByTitle(/show subtasks/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Toggle', () => {
|
||||
it('should toggle subtasks visibility when button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(screen.getByTestId('subtasks-display')).toBeInTheDocument();
|
||||
|
||||
// Click again to hide
|
||||
const hideButton = screen.getByTitle(/hide subtasks/i);
|
||||
await user.click(hideButton);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('subtasks-display')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should persist subtasks visibility state', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(screen.getByTestId('subtasks-display')).toBeInTheDocument();
|
||||
|
||||
// Re-render component - subtasks should still be visible
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
expect(screen.getByTestId('subtasks-display')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Progress Bar', () => {
|
||||
it('should show progress bar when subtasks are expanded', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(screen.getByTestId('subtasks-progress')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show progress bar when subtasks are collapsed', async () => {
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('subtasks-progress')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should calculate progress correctly', async () => {
|
||||
const user = userEvent.setup();
|
||||
const subtasksWithProgress = [
|
||||
createMockTask({
|
||||
id: 2,
|
||||
name: 'Subtask 1',
|
||||
parent_task_id: 1,
|
||||
status: 'done',
|
||||
}),
|
||||
createMockTask({
|
||||
id: 3,
|
||||
name: 'Subtask 2',
|
||||
parent_task_id: 1,
|
||||
status: 'not_started',
|
||||
}),
|
||||
createMockTask({
|
||||
id: 4,
|
||||
name: 'Subtask 3',
|
||||
parent_task_id: 1,
|
||||
status: 'done',
|
||||
}),
|
||||
];
|
||||
mockFetchSubtasks.mockResolvedValue(subtasksWithProgress);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
// 2 out of 3 subtasks are done = 66.67%
|
||||
expect(screen.getByText('2 of 3 completed')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Interaction', () => {
|
||||
it('should handle subtask click events', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskUpdate = jest.fn();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(
|
||||
<TaskItem
|
||||
{...mockProps}
|
||||
task={mockParentTask}
|
||||
onTaskUpdate={onTaskUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
const subtaskItem = screen.getByText('Subtask 1');
|
||||
await user.click(subtaskItem);
|
||||
|
||||
// Should handle subtask click appropriately
|
||||
expect(onTaskUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prevent task selection when clicking on subtasks area', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskClick = jest.fn();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(
|
||||
<TaskItem
|
||||
{...mockProps}
|
||||
task={mockParentTask}
|
||||
onTaskClick={onTaskClick}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
const subtasksDisplay = screen.getByTestId('subtasks-display');
|
||||
await user.click(subtasksDisplay);
|
||||
|
||||
// Should not trigger task selection
|
||||
expect(onTaskClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update parent task when subtask is completed', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskUpdate = jest.fn();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(
|
||||
<TaskItem
|
||||
{...mockProps}
|
||||
task={mockParentTask}
|
||||
onTaskUpdate={onTaskUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
const subtaskCheckbox = screen.getByTestId('subtask-checkbox-2');
|
||||
await user.click(subtaskCheckbox);
|
||||
|
||||
expect(onTaskUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: mockParentTask.id,
|
||||
status: 'done', // Parent should be completed when all subtasks are done
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtasks Layout', () => {
|
||||
it('should render subtasks with proper indentation', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
const subtasksDisplay = screen.getByTestId('subtasks-display');
|
||||
expect(subtasksDisplay).toHaveClass('ml-6'); // Indented subtasks
|
||||
});
|
||||
|
||||
it('should not show task options for subtasks', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
// Subtasks should not have the same options as parent tasks
|
||||
const subtaskItems = screen.getAllByText(/subtask/i);
|
||||
expect(subtaskItems).toHaveLength(2);
|
||||
|
||||
// Should not show edit/delete options for subtasks
|
||||
expect(
|
||||
screen.queryByTestId('subtask-edit-button')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Modal Integration', () => {
|
||||
it('should show subtasks section in task modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchSubtasks.mockResolvedValue(mockSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
// Click on task to open modal
|
||||
const taskHeader = screen.getByText(mockParentTask.name);
|
||||
await user.click(taskHeader);
|
||||
|
||||
// Modal should contain subtasks section
|
||||
expect(screen.getByText('Subtasks')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty subtasks array', async () => {
|
||||
mockFetchSubtasks.mockResolvedValue([]);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
// Should not show subtasks button if no subtasks
|
||||
await screen.findByText(mockParentTask.name);
|
||||
expect(
|
||||
screen.queryByTitle(/show subtasks/i)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle subtasks with null parent_task_id', async () => {
|
||||
const invalidSubtasks = [
|
||||
createMockTask({
|
||||
id: 2,
|
||||
name: 'Invalid Subtask',
|
||||
parent_task_id: null,
|
||||
}),
|
||||
];
|
||||
mockFetchSubtasks.mockResolvedValue(invalidSubtasks);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
// Should handle gracefully and not crash
|
||||
await screen.findByText(mockParentTask.name);
|
||||
});
|
||||
|
||||
it('should handle very long subtask names', async () => {
|
||||
const user = userEvent.setup();
|
||||
const longSubtask = createMockTask({
|
||||
id: 2,
|
||||
name: 'This is a very long subtask name that should be handled properly without breaking the layout',
|
||||
parent_task_id: 1,
|
||||
});
|
||||
mockFetchSubtasks.mockResolvedValue([longSubtask]);
|
||||
|
||||
render(<TaskItem {...mockProps} task={mockParentTask} />);
|
||||
|
||||
const subtasksButton = await screen.findByTitle(/show subtasks/i);
|
||||
await user.click(subtasksButton);
|
||||
|
||||
expect(screen.getByText(longSubtask.name)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,454 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TaskSubtasksSection } from '../TaskSubtasksSection';
|
||||
import { mockTask, mockSubtasks } from '@/__tests__/testUtils';
|
||||
import { Task } from '@/entities/Task';
|
||||
|
||||
// Mock the tasksService
|
||||
const mockFetchSubtasks = jest.fn();
|
||||
jest.mock('@/utils/tasksService', () => ({
|
||||
fetchSubtasks: mockFetchSubtasks,
|
||||
}));
|
||||
|
||||
describe('TaskSubtasksSection', () => {
|
||||
const mockProps = {
|
||||
task: mockTask,
|
||||
subtasks: [] as Task[],
|
||||
onSubtasksChange: jest.fn(),
|
||||
isFormSubmitting: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render subtasks section title', () => {
|
||||
render(<TaskSubtasksSection {...mockProps} />);
|
||||
expect(screen.getByText('Subtasks')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render add subtask button', () => {
|
||||
render(<TaskSubtasksSection {...mockProps} />);
|
||||
expect(
|
||||
screen.getByRole('button', { name: /add subtask/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render existing subtasks', () => {
|
||||
const propsWithSubtasks = {
|
||||
...mockProps,
|
||||
subtasks: mockSubtasks,
|
||||
};
|
||||
render(<TaskSubtasksSection {...propsWithSubtasks} />);
|
||||
|
||||
expect(screen.getByDisplayValue('Subtask 1')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('Subtask 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show empty state when no subtasks', () => {
|
||||
render(<TaskSubtasksSection {...mockProps} />);
|
||||
expect(screen.getByText(/no subtasks yet/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Adding Subtasks', () => {
|
||||
it('should add new subtask when add button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const addButton = screen.getByRole('button', {
|
||||
name: /add subtask/i,
|
||||
});
|
||||
await user.click(addButton);
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
name: '',
|
||||
isNew: true,
|
||||
tempId: expect.any(String),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should focus on new subtask input when added', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const addButton = screen.getByRole('button', {
|
||||
name: /add subtask/i,
|
||||
});
|
||||
await user.click(addButton);
|
||||
|
||||
// Re-render with new subtask
|
||||
const newSubtask = {
|
||||
name: '',
|
||||
isNew: true,
|
||||
tempId: 'temp-1',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[newSubtask] as any}
|
||||
/>
|
||||
);
|
||||
|
||||
const newInput = screen.getByDisplayValue('');
|
||||
expect(newInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should allow typing in new subtask input', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
const newSubtask = {
|
||||
name: '',
|
||||
isNew: true,
|
||||
tempId: 'temp-1',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[newSubtask] as any}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const input = screen.getByDisplayValue('');
|
||||
await user.type(input, 'New subtask name');
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
name: 'New subtask name',
|
||||
isNew: true,
|
||||
tempId: 'temp-1',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Editing Subtasks', () => {
|
||||
it('should allow editing existing subtask name', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={mockSubtasks}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const input = screen.getByDisplayValue('Subtask 1');
|
||||
await user.clear(input);
|
||||
await user.type(input, 'Updated subtask name');
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
id: 2,
|
||||
name: 'Updated subtask name',
|
||||
isEdited: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 3,
|
||||
name: 'Subtask 2',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark subtask as edited when name changes', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={mockSubtasks}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const input = screen.getByDisplayValue('Subtask 1');
|
||||
await user.type(input, ' edited');
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
id: 2,
|
||||
name: 'Subtask 1 edited',
|
||||
isEdited: true,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 3,
|
||||
name: 'Subtask 2',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Removing Subtasks', () => {
|
||||
it('should show remove button for each subtask', () => {
|
||||
render(
|
||||
<TaskSubtasksSection {...mockProps} subtasks={mockSubtasks} />
|
||||
);
|
||||
|
||||
const removeButtons = screen.getAllByRole('button', {
|
||||
name: /remove subtask/i,
|
||||
});
|
||||
expect(removeButtons).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should remove subtask when remove button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={mockSubtasks}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const removeButtons = screen.getAllByRole('button', {
|
||||
name: /remove subtask/i,
|
||||
});
|
||||
await user.click(removeButtons[0]);
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
id: 3,
|
||||
name: 'Subtask 2',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove new subtask immediately', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
const newSubtask = {
|
||||
name: 'New subtask',
|
||||
isNew: true,
|
||||
tempId: 'temp-1',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[newSubtask] as any}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const removeButton = screen.getByRole('button', {
|
||||
name: /remove subtask/i,
|
||||
});
|
||||
await user.click(removeButton);
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Subtask Validation', () => {
|
||||
it('should show validation error for empty subtask name', () => {
|
||||
const subtaskWithEmptyName = {
|
||||
...mockSubtasks[0],
|
||||
name: '',
|
||||
isEdited: true,
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[subtaskWithEmptyName]}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/subtask name is required/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show validation error for duplicate subtask names', () => {
|
||||
const duplicateSubtasks = [
|
||||
{ ...mockSubtasks[0], name: 'Duplicate Name' },
|
||||
{ ...mockSubtasks[1], name: 'Duplicate Name' },
|
||||
];
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={duplicateSubtasks}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/duplicate subtask names are not allowed/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not save until validation passes', () => {
|
||||
const subtaskWithEmptyName = {
|
||||
...mockSubtasks[0],
|
||||
name: '',
|
||||
isEdited: true,
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[subtaskWithEmptyName]}
|
||||
/>
|
||||
);
|
||||
|
||||
// Should show validation error
|
||||
expect(
|
||||
screen.getByText(/subtask name is required/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Submission State', () => {
|
||||
it('should disable inputs when form is submitting', () => {
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={mockSubtasks}
|
||||
isFormSubmitting={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
inputs.forEach((input) => {
|
||||
expect(input).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable add button when form is submitting', () => {
|
||||
render(
|
||||
<TaskSubtasksSection {...mockProps} isFormSubmitting={true} />
|
||||
);
|
||||
|
||||
const addButton = screen.getByRole('button', {
|
||||
name: /add subtask/i,
|
||||
});
|
||||
expect(addButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable remove buttons when form is submitting', () => {
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={mockSubtasks}
|
||||
isFormSubmitting={true}
|
||||
/>
|
||||
);
|
||||
|
||||
const removeButtons = screen.getAllByRole('button', {
|
||||
name: /remove subtask/i,
|
||||
});
|
||||
removeButtons.forEach((button) => {
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Keyboard Navigation', () => {
|
||||
it('should move focus to next input when pressing Tab', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection {...mockProps} subtasks={mockSubtasks} />
|
||||
);
|
||||
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
await user.click(inputs[0]);
|
||||
await user.tab();
|
||||
|
||||
expect(inputs[1]).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should add new subtask when pressing Enter on add button', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSubtasksChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
onSubtasksChange={onSubtasksChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const addButton = screen.getByRole('button', {
|
||||
name: /add subtask/i,
|
||||
});
|
||||
await user.click(addButton);
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(onSubtasksChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper ARIA labels', () => {
|
||||
render(
|
||||
<TaskSubtasksSection {...mockProps} subtasks={mockSubtasks} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /add subtask/i })
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: /remove subtask/i })
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should have proper form labels', () => {
|
||||
render(
|
||||
<TaskSubtasksSection {...mockProps} subtasks={mockSubtasks} />
|
||||
);
|
||||
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
inputs.forEach((input, index) => {
|
||||
expect(input).toHaveAttribute(
|
||||
'placeholder',
|
||||
`Subtask ${index + 1} name`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should announce validation errors to screen readers', () => {
|
||||
const subtaskWithEmptyName = {
|
||||
...mockSubtasks[0],
|
||||
name: '',
|
||||
isEdited: true,
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskSubtasksSection
|
||||
{...mockProps}
|
||||
subtasks={[subtaskWithEmptyName]}
|
||||
/>
|
||||
);
|
||||
|
||||
const errorMessage = screen.getByText(/subtask name is required/i);
|
||||
expect(errorMessage).toHaveAttribute('role', 'alert');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue