Skip to content

Commit

Permalink
Add Unit Tests for Refactored TaskDetails Subcomponents (#1245)
Browse files Browse the repository at this point in the history
* refactor(TaskDetails): Modularize and optimize TaskDetails component for improved maintainability and performance

- Broke down TaskDetails component into smaller, focused subcomponents: TaskHeader, TaskDescription, TaskDetailsSection, TaskParticipants, TaskDates, TaskDependencies, and TaskProgress.
- Maintained consistent styling using existing CSS classes from task-details.module.scss.
- Ensured all functionalities, including editing, API integration, and state management, remain intact.
- Added performance optimizations using React best practices, such as useCallback and useMemo where applicable.
- Handled loading and error states, as well as edge cases like empty data gracefully.

* fix(TaskDetails): Correct data type issues and ensure proper conditional rendering

- Resolved type issues, ensuring  is correctly handled as a number.
- Fixed conditional rendering to prevent the edit button from appearing for unauthorized users.
- Ensured consistent date formatting using  utility.
- Improved type safety across the TaskDetails subcomponents to prevent future issues.
- Maintained all functionality from previous refactor, including state management and API integration.

* fix(task-details): Correct the position of status and link fields

- Adjusted the position of the 'Status' and 'Link' fields within the TaskDetailsSection component to ensure they are displayed correctly.
- Ensured that the 'Status' dropdown is only visible in edit mode and does not appear twice.
- Verified that the layout matches the design specifications when switching between view and edit modes.

* chore: remove unnecessary comments from the code

* fix: restore original styling for TaskDates and TaskParticipants components

- Reverted changes in the component structure that affected the layout and styling.
- Ensured the CSS classes are applied correctly and match the pre-refactor styling.
- Adjusted the JSX structure to maintain the original visual layout.

* test: Add unit tests for TaskHeader component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskDescription component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskProgress component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskParticipants component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskDates component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskDetailsSection component of refactored TaskDetails subcomponents

* test: Add unit tests for TaskDependencies component of refactored TaskDetails subcomponents

* chore: remove react memo from every component as it was unnecessary

* chore: remove react memo from every component as it was unnecessary

* refactor: TaskDates component to conditionally render Details based on extension request status

* refactor: switch to named exports for all of the sub-components

* chore: remove any from TaskDetails component and replace them with appropiates type

* chore

* Delete src/components/taskDetails/TaskProgress.tsx

* Delete src/components/taskDetails/TaskParticipants.tsx

* Delete src/components/taskDetails/TaskHeader.tsx

* Delete src/components/taskDetails/TaskDetailsSection.tsx

* Delete src/components/taskDetails/TaskDescription.tsx

* Delete src/components/taskDetails/TaskDependencies.tsx

* Delete src/components/taskDetails/TaskDates.tsx

* chore: removed default export from every  sub-component as we are already using named export

* chore: removed default export from every  sub-component as we are already using named export

* refactor: refactor test codes to use named export instead of default exports

* refactor(TaskDetails): remove unnecessary TaskProgress and TaskDependencies components and directly use underlying components

* chore: remove test code of taskdependency and taskprogress as i have already used them in underlying taskdetails component

* chore: remove test code of taskdependency and taskprogress as i have already used them in underlying taskdetails component

* refactor: refactor code to simplyfy code reading used is else instead of ternary operator

* chore: remove extra Taskcontainer wrap that was impacting styling of update progress

---------

Co-authored-by: Ashif Khan <54736284+ashifkhn@users.noreply.github.com>
Co-authored-by: Amit Prakash <34869115+iamitprakash@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 3, 2024
1 parent 075a72e commit 933e06d
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 96 deletions.
28 changes: 28 additions & 0 deletions __tests__/Unit/Components/Tasks/TaskDates.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TaskDates } from '@/components/taskDetails/TaskDates';

const mockSetNewEndOnDate = jest.fn();
const mockHandleBlurOfEndsOn = jest.fn();

describe('TaskDates Component', () => {
it('should render input field for End On date when in editing mode', () => {
render(
<TaskDates
isEditing={true}
isUserAuthorized={true}
startedOn="2024-03-30T11:20:00Z"
endsOn={1700000000}
newEndOnDate="2024-03-30"
setNewEndOnDate={mockSetNewEndOnDate}
handleBlurOfEndsOn={mockHandleBlurOfEndsOn}
isExtensionRequestPending={false}
taskId="1"
/>
);

const input = screen.getByTestId('endsOnTaskDetails');
expect(input).toBeInTheDocument();
fireEvent.blur(input);
expect(mockHandleBlurOfEndsOn).toHaveBeenCalled();
});
});
96 changes: 0 additions & 96 deletions __tests__/Unit/Components/Tasks/TaskDependency.test.tsx

This file was deleted.

55 changes: 55 additions & 0 deletions __tests__/Unit/Components/Tasks/TaskDescription.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TaskDescription } from '@/components/taskDetails/TaskDescription';

const mockHandleChange = jest.fn();

describe('TaskDescription Component', () => {
it('should renders the task description when not editing', () => {
render(
<TaskDescription
isEditing={false}
purpose="Test Purpose"
handleChange={mockHandleChange}
/>
);
expect(screen.getByText('Test Purpose')).toBeInTheDocument();
});

it('should renders "No description available" when purpose is empty', () => {
render(
<TaskDescription
isEditing={false}
purpose=""
handleChange={mockHandleChange}
/>
);
expect(
screen.getByText('No description available')
).toBeInTheDocument();
});

it('should renders textarea when in editing mode', () => {
render(
<TaskDescription
isEditing={true}
purpose="Test Purpose"
handleChange={mockHandleChange}
/>
);
expect(screen.getByTestId('purpose-textarea')).toBeInTheDocument();
});

it('should calls handleChange when textarea value changes', () => {
render(
<TaskDescription
isEditing={true}
purpose="Test Purpose"
handleChange={mockHandleChange}
/>
);
fireEvent.change(screen.getByTestId('purpose-textarea'), {
target: { value: 'New Purpose' },
});
expect(mockHandleChange).toHaveBeenCalled();
});
});
97 changes: 97 additions & 0 deletions __tests__/Unit/Components/Tasks/TaskDetailsSection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { TaskDetailsSection } from '@/components/taskDetails/TaskDetailsSection';
import '@testing-library/jest-dom/extend-expect';
import { TASK } from '../../../../__mocks__/db/tasks';
import { useRouter } from 'next/router';
import { api } from '@/app/services/api';

jest.mock('next/router', () => ({
useRouter: jest.fn(),
}));

const createMockStore = () => {
return configureStore({
reducer: {
user: (state = { roles: {} }) => state,
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});
};

describe('TaskDetailsSection Component', () => {
const mockHandleTaskStatusUpdate = jest.fn();
const mockRouter = {
query: { dev: true },
};

beforeEach(() => {
(useRouter as jest.Mock).mockReturnValue(mockRouter);
});

const defaultProps = {
isEditing: false,
type: TASK.type,
priority: TASK.priority,
status: TASK.status,
link: TASK.featureUrl,
percentCompleted: TASK.percentCompleted,
handleTaskStatusUpdate: mockHandleTaskStatusUpdate,
taskDetailsData: TASK,
};

it('should render task details correctly when not in editing mode', () => {
render(
<Provider store={createMockStore()}>
<TaskDetailsSection {...defaultProps} />
</Provider>
);

expect(screen.getByText(TASK.type)).toBeInTheDocument();
expect(screen.getByText(TASK.priority)).toBeInTheDocument();
expect(screen.getByText(TASK.status)).toBeInTheDocument();

const linkElement = screen.getByRole('link', {
name: /open github issue/i,
});
expect(linkElement).toHaveAttribute('href', TASK.featureUrl);
});

it('should render task status dropdown when in editing mode', () => {
render(
<Provider store={createMockStore()}>
<TaskDetailsSection {...defaultProps} isEditing={true} />
</Provider>
);
expect(screen.getByRole('combobox')).toBeInTheDocument();
expect(screen.queryByText(TASK.status)).toBeNull();
});

it('should call handleTaskStatusUpdate when task status is changed', () => {
render(
<Provider store={createMockStore()}>
<TaskDetailsSection {...defaultProps} isEditing={true} />
</Provider>
);
const dropdown = screen.getByRole('combobox');
fireEvent.change(dropdown, { target: { value: 'DONE' } });

expect(mockHandleTaskStatusUpdate).toHaveBeenCalledWith({
newStatus: 'DONE',
});
});

it('should render progress container correctly', () => {
render(
<Provider store={createMockStore()}>
<TaskDetailsSection {...defaultProps} />
</Provider>
);
expect(
screen.getByText(`${TASK.percentCompleted}%`)
).toBeInTheDocument();
});
});
61 changes: 61 additions & 0 deletions __tests__/Unit/Components/Tasks/TaskHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TaskHeader } from '@/components/taskDetails/TaskHeader';
import { ButtonProps } from '@/interfaces/taskDetails.type';

const mockSetIsEditing = jest.fn();
const mockOnSave = jest.fn();
const mockOnCancel = jest.fn();
const mockHandleChange = jest.fn();

const renderTaskHeader = (isEditing = false) => {
return render(
<TaskHeader
isEditing={isEditing}
setIsEditing={mockSetIsEditing}
onSave={mockOnSave}
onCancel={mockOnCancel}
title="Test Title"
handleChange={mockHandleChange}
isUserAuthorized={true}
/>
);
};

describe('TaskHeader Component', () => {
it('should renders the task title correctly when not editing', () => {
renderTaskHeader(false);
expect(screen.getByText('Test Title')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Edit' })
).toBeInTheDocument();
});

it('should renders textarea for title when in editing mode', () => {
renderTaskHeader(true);
expect(screen.getByTestId('title-textarea')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Save' })
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Cancel' })
).toBeInTheDocument();
});

it('should calls setIsEditing with true when Edit button is clicked', () => {
renderTaskHeader(false);
fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
expect(mockSetIsEditing).toHaveBeenCalledWith(true);
});

it('should calls onSave when Save button is clicked', () => {
renderTaskHeader(true);
fireEvent.click(screen.getByRole('button', { name: 'Save' }));
expect(mockOnSave).toHaveBeenCalled();
});

it('should calls onCancel when Cancel button is clicked', () => {
renderTaskHeader(true);
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(mockOnCancel).toHaveBeenCalled();
});
});
49 changes: 49 additions & 0 deletions __tests__/Unit/Components/Tasks/TaskParticipants.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TaskParticipants } from '@/components/taskDetails/TaskParticipants';
import { Provider } from 'react-redux';
import { store } from '@/app/store';

const mockHandleAssignment = jest.fn();
const mockHandleAssigneSelect = jest.fn();
const mockSetShowSuggestion = jest.fn();

const renderWithProvider = (ui: React.ReactElement) => {
return render(<Provider store={store()}>{ui}</Provider>);
};

describe('TaskParticipants Component', () => {
it('should render the assignee name correctly when not editing', () => {
renderWithProvider(
<TaskParticipants
isEditing={false}
isUserAuthorized={true}
assigneeName="John Doe"
showSuggestion={false}
handleAssignment={mockHandleAssignment}
handleAssigneSelect={mockHandleAssigneSelect}
setShowSuggestion={mockSetShowSuggestion}
/>
);

expect(screen.getByText('John Doe')).toBeInTheDocument();
});

it('should render the Suggestions component when editing', () => {
renderWithProvider(
<TaskParticipants
isEditing={true}
isUserAuthorized={true}
assigneeName="John Doe"
showSuggestion={true}
handleAssignment={mockHandleAssignment}
handleAssigneSelect={mockHandleAssigneSelect}
setShowSuggestion={mockSetShowSuggestion}
/>
);

const input = screen.getByRole('textbox');
expect(input).toBeInTheDocument();
fireEvent.change(input, { target: { value: 'Jane Doe' } });
expect(mockHandleAssignment).toHaveBeenCalled();
});
});

0 comments on commit 933e06d

Please sign in to comment.