diff --git a/frontend/__tests__/setup.ts b/frontend/__tests__/setup.ts
deleted file mode 100644
index b2aafeb..0000000
--- a/frontend/__tests__/setup.ts
+++ /dev/null
@@ -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;
-});
\ No newline at end of file
diff --git a/frontend/__tests__/testUtils.tsx b/frontend/__tests__/testUtils.tsx
deleted file mode 100644
index 9aef117..0000000
--- a/frontend/__tests__/testUtils.tsx
+++ /dev/null
@@ -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
{children}
;
-};
-
-const customRender = (
- ui: React.ReactElement,
- options?: Omit
-) => 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 => ({
- ...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',
- }));
-};
diff --git a/frontend/components/Task/__tests__/TaskHeader.test.tsx b/frontend/components/Task/__tests__/TaskHeader.test.tsx
deleted file mode 100644
index f1920b4..0000000
--- a/frontend/components/Task/__tests__/TaskHeader.test.tsx
+++ /dev/null
@@ -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();
-
- 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();
-
- expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
- });
-
- it('should show hide subtasks button when subtasks are expanded', () => {
- render(
-
- );
-
- expect(screen.getByTitle(/hide subtasks/i)).toBeInTheDocument();
- });
-
- it('should show subtasks button for completed tasks', () => {
- render(
-
- );
-
- expect(screen.getByTitle(/show subtasks/i)).toBeInTheDocument();
- });
-
- it('should not show subtasks button for archived tasks', () => {
- const archivedTask = createMockTask({ status: 'archived' });
- render(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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();
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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();
-
- // 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(
-
- );
-
- 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();
-
- 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(
-
- );
-
- // 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(
-
- );
-
- // 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();
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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();
-
- 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();
-
- 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();
-
- const subtasksButton = screen.getByTitle(/show subtasks/i);
- const buttonContainer = subtasksButton.closest('.flex');
- expect(buttonContainer).toHaveClass('items-center');
- });
- });
-});
diff --git a/frontend/components/Task/__tests__/TaskItem.test.tsx b/frontend/components/Task/__tests__/TaskItem.test.tsx
deleted file mode 100644
index de38803..0000000
--- a/frontend/components/Task/__tests__/TaskItem.test.tsx
+++ /dev/null
@@ -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();
-
- expect(
- screen.queryByTestId('subtasks-display')
- ).not.toBeInTheDocument();
- });
-
- it('should show subtasks when showSubtasks is true and task has subtasks', async () => {
- mockFetchSubtasks.mockResolvedValue(mockSubtasks);
-
- render();
-
- // 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();
-
- expect(
- screen.queryByTestId('subtasks-display')
- ).not.toBeInTheDocument();
- });
-
- it('should show subtasks for completed tasks', async () => {
- const completedTask = createMockTask({ status: 'done' });
- mockFetchSubtasks.mockResolvedValue(mockSubtasks);
-
- render();
-
- // 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();
-
- expect(mockFetchSubtasks).toHaveBeenCalledWith(mockParentTask.id);
- });
-
- it('should show loading state while fetching subtasks', async () => {
- mockFetchSubtasks.mockImplementation(
- () => new Promise((resolve) => setTimeout(resolve, 100))
- );
-
- render();
-
- expect(screen.getByText(/loading/i)).toBeInTheDocument();
- });
-
- it('should handle subtasks fetch error gracefully', async () => {
- mockFetchSubtasks.mockRejectedValue(new Error('Failed to fetch'));
-
- render();
-
- await screen.findByText(/error loading subtasks/i);
- });
-
- it('should update hasSubtasks state based on fetched data', async () => {
- mockFetchSubtasks.mockResolvedValue(mockSubtasks);
-
- render();
-
- // 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();
-
- 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();
-
- 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();
-
- 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();
-
- 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();
-
- 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();
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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();
-
- 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();
-
- 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();
-
- // 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();
-
- // 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();
-
- // 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();
-
- const subtasksButton = await screen.findByTitle(/show subtasks/i);
- await user.click(subtasksButton);
-
- expect(screen.getByText(longSubtask.name)).toBeInTheDocument();
- });
- });
-});
diff --git a/frontend/components/Task/__tests__/TaskSubtasksSection.test.tsx b/frontend/components/Task/__tests__/TaskSubtasksSection.test.tsx
deleted file mode 100644
index 4113831..0000000
--- a/frontend/components/Task/__tests__/TaskSubtasksSection.test.tsx
+++ /dev/null
@@ -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();
- expect(screen.getByText('Subtasks')).toBeInTheDocument();
- });
-
- it('should render add subtask button', () => {
- render();
- expect(
- screen.getByRole('button', { name: /add subtask/i })
- ).toBeInTheDocument();
- });
-
- it('should render existing subtasks', () => {
- const propsWithSubtasks = {
- ...mockProps,
- subtasks: mockSubtasks,
- };
- render();
-
- expect(screen.getByDisplayValue('Subtask 1')).toBeInTheDocument();
- expect(screen.getByDisplayValue('Subtask 2')).toBeInTheDocument();
- });
-
- it('should show empty state when no subtasks', () => {
- render();
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- // 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(
-
- );
-
- const inputs = screen.getAllByRole('textbox');
- inputs.forEach((input) => {
- expect(input).toBeDisabled();
- });
- });
-
- it('should disable add button when form is submitting', () => {
- render(
-
- );
-
- const addButton = screen.getByRole('button', {
- name: /add subtask/i,
- });
- expect(addButton).toBeDisabled();
- });
-
- it('should disable remove buttons when form is submitting', () => {
- render(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- 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(
-
- );
-
- const errorMessage = screen.getByText(/subtask name is required/i);
- expect(errorMessage).toHaveAttribute('role', 'alert');
- });
- });
-});