diff --git a/__mocks__/handlers.ts b/__mocks__/handlers.ts index 5c00a5993..72fb97b65 100644 --- a/__mocks__/handlers.ts +++ b/__mocks__/handlers.ts @@ -11,6 +11,7 @@ import issuesHandler from './handlers/issues.handler'; import standupHandler from './handlers/standup.handler'; import { prsHandler } from './handlers/pull-requests.handler'; import { progressHandler } from './handlers/progresses.handler'; +import { taskRequestSuccessHandlers } from './handlers/task-request.handler'; const handlers = [ ...mineTasksHandler, @@ -28,6 +29,7 @@ const handlers = [ ...issuesHandler, ...standupHandler, ...prsHandler, + ...taskRequestSuccessHandlers ]; export default handlers; diff --git a/__mocks__/handlers/task-request.handler.ts b/__mocks__/handlers/task-request.handler.ts new file mode 100644 index 000000000..955595ffe --- /dev/null +++ b/__mocks__/handlers/task-request.handler.ts @@ -0,0 +1,18 @@ +import { rest } from 'msw'; + +const URL = process.env.NEXT_PUBLIC_BASE_URL; + +export const taskRequestSuccessHandlers = [ + rest.post(`${URL}/taskRequests/addOrUpdate`, (_, res, ctx) => { + return res(ctx.status(201), ctx.json({ message: 'Task request successfully created' })); + }), +]; + +export const taskRequestErrorHandler = [ + rest.post(`${URL}/taskRequests/addOrUpdate`, (_, res, ctx) => { + return res( + ctx.status(409), + ctx.json({ message: 'taskId not provided' }) + ); + }), +]; diff --git a/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx b/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx index 035c04585..abde6f808 100644 --- a/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx +++ b/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx @@ -12,6 +12,8 @@ import { ButtonProps, TextAreaProps } from '@/interfaces/taskDetails.type'; import { ToastContainer } from 'react-toastify'; import * as progressQueries from '@/app/services/progressesApi'; import Details from '@/components/taskDetails/Details'; +import { taskRequestErrorHandler } from '../../../../__mocks__/handlers/task-request.handler'; +import { taskDetailsHandler } from '../../../../__mocks__/handlers/task-details.handler'; const details = { url: 'https://realdevsquad.com/tasks/6KhcLU3yr45dzjQIVm0J/details', @@ -247,6 +249,8 @@ describe('TaskDetails Page', () => { }); test('should update the title and description with the new values', async () => { + server.use(...taskDetailsHandler); + renderWithRouter( @@ -385,6 +389,85 @@ describe('Update Progress button', () => { }); }); +describe('Task Details > Task Request', () => { + it('should show task request button when dev is true', async () => { + renderWithRouter( + + + , + { query: { dev: 'true' } } + ); + + await waitFor(() => { + expect( + screen.queryByRole('button', { name: /request for task/i }) + ).not.toBeNull(); + }); + }); + + it('should not show task request button when dev is false', async () => { + renderWithRouter( + + + + ); + + await waitFor(() => { + expect( + screen.queryByRole('button', { name: /request for task/i }) + ).toBeNull(); + }); + }); + + it('Success toast should be shown on success', async () => { + renderWithRouter( + + + + , + { query: { dev: 'true' } } + ); + await waitFor(() => { + expect( + screen.queryByRole('button', { name: /request for task/i }) + ).not.toBeNull(); + }); + + const taskRequestButton = screen.getByRole('button', { + name: /request for task/i, + }); + fireEvent.click(taskRequestButton); + + await waitFor(() => { + screen.getByText(/successfully requested for task/i); + }); + }); + + it('Error toast should be shown on error', async () => { + server.use(...taskRequestErrorHandler); + + renderWithRouter( + + + + , + { query: { dev: 'true' } } + ); + await waitFor(() => { + screen.getByRole('button', { name: /request for task/i }); + }); + + const taskRequestButton = screen.getByRole('button', { + name: /request for task/i, + }); + fireEvent.click(taskRequestButton); + + await waitFor(() => { + expect(screen.queryByText(/taskId not provided/i)).not.toBeNull(); + }); + }); +}); + describe('TaskDependency', () => { it('should renders task titles', () => { render( diff --git a/__tests__/Unit/hooks/taskRequestApi.test.tsx b/__tests__/Unit/hooks/taskRequestApi.test.tsx new file mode 100644 index 000000000..eb9f871c0 --- /dev/null +++ b/__tests__/Unit/hooks/taskRequestApi.test.tsx @@ -0,0 +1,76 @@ +import { store } from '@/app/store'; +import { setupServer } from 'msw/node'; +import { Provider } from 'react-redux'; +import handlers from '../../../__mocks__/handlers'; +import { PropsWithChildren } from 'react'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { useAddOrUpdateMutation } from '@/app/services/taskRequestApi'; +import { taskRequestErrorHandler } from '../../../__mocks__/handlers/task-request.handler'; + +function Wrapper({ + children, +}: PropsWithChildren>): JSX.Element { + return {children}; +} + +const server = setupServer(...handlers); + +describe('useAddOrUpdateMutation', () => { + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + it('should update the user', async () => { + const { result, waitForNextUpdate } = renderHook( + () => useAddOrUpdateMutation(), + { wrapper: Wrapper } + ); + + const [addTask, initialResponse] = result.current; + expect(initialResponse.data).toBeUndefined(); + expect(initialResponse.isLoading).toBe(false); + + act(() => { + addTask({ taskId: 'taskId', userId: 'userId' }); + }); + + expect(result.current[1].isLoading).toBe(true); + + await act(() => waitForNextUpdate()); + + const response = result.current[1]; + expect(response.data).not.toBeUndefined(); + expect(response.data?.message).toBe( + 'Task request successfully created' + ); + expect(response.isLoading).toBe(false); + expect(response.isSuccess).toBe(true); + expect(response.isError).toBe(false); + }); + + it('should return error while updating the user', async () => { + server.use(...taskRequestErrorHandler); + const { result, waitForNextUpdate } = renderHook( + () => useAddOrUpdateMutation(), + { wrapper: Wrapper } + ); + + const [addTask, initialResponse] = result.current; + expect(initialResponse.data).toBeUndefined(); + expect(initialResponse.isLoading).toBe(false); + + act(() => { + addTask({ taskId: 'taskId', userId: 'userId' }); + }); + + expect(result.current[1].isLoading).toBe(true); + + await act(() => waitForNextUpdate()); + + const response = result.current[1]; + expect(response.error).not.toBeUndefined(); + expect(response.isLoading).toBe(false); + expect(response.isSuccess).toBe(false); + expect(response.isError).toBe(true); + }); +}); diff --git a/src/app/services/api.ts b/src/app/services/api.ts index 407b2a76d..5105d0d66 100644 --- a/src/app/services/api.ts +++ b/src/app/services/api.ts @@ -25,6 +25,8 @@ export const api = createApi({ 'Challenges', 'Idle_Members', 'Progress_Details', + 'User_Standup', + 'TASK_REQUEST', ], /** * This api has endpoints injected in adjacent files, diff --git a/src/app/services/taskRequestApi.ts b/src/app/services/taskRequestApi.ts new file mode 100644 index 000000000..6cd4b6074 --- /dev/null +++ b/src/app/services/taskRequestApi.ts @@ -0,0 +1,28 @@ +import { api } from './api'; + +type AddOrUpdateMutationQuery = { + taskId: string; + userId: string; +}; + +type AddOrUpdateMutationQueryRes = + | { message: string } + | { message: string; requestors: string[] }; + +export const taskRequestApi = api.injectEndpoints({ + endpoints: (build) => ({ + addOrUpdate: build.mutation< + AddOrUpdateMutationQueryRes, + AddOrUpdateMutationQuery + >({ + query: (body) => ({ + url: 'taskRequests/addOrUpdate', + method: 'POST', + body, + }), + invalidatesTags: ['TASK_REQUEST'], + }), + }), +}); + +export const { useAddOrUpdateMutation } = taskRequestApi; diff --git a/src/components/taskDetails/index.tsx b/src/components/taskDetails/index.tsx index 439a7f285..fbe32dfc1 100644 --- a/src/components/taskDetails/index.tsx +++ b/src/components/taskDetails/index.tsx @@ -27,6 +27,7 @@ import TaskDependency from '@/components/taskDetails/taskDependency'; import { useGetProgressDetailsQuery } from '@/app/services/progressesApi'; import { ProgressDetailsData } from '@/types/standup.type'; import { getDateFromTimestamp } from '@/utils/getDateFromTimestamp'; +import { useAddOrUpdateMutation } from '@/app/services/taskRequestApi'; export function Button(props: ButtonProps) { const { buttonName, clickHandler, value } = props; @@ -63,7 +64,7 @@ const TaskDetails: FC = ({ taskID }) => { const { query } = router; const isDevModeEnabled = query.dev === 'true' ? true : false; - const { isUserAuthorized } = useUserData(); + const { isUserAuthorized, data: userData } = useUserData(); const [isEditing, setIsEditing] = useState(false); const { data, isError, isLoading, isFetching } = @@ -80,6 +81,9 @@ const TaskDetails: FC = ({ taskID }) => { taskDetailsDataType['taskData'] | undefined >(data?.taskData); + const [addOrUpdateTaskRequest, taskRequestUpdateStatus] = + useAddOrUpdateMutation(); + useEffect(() => { if (data?.taskData) { setEditedTaskDetails(data.taskData); @@ -116,6 +120,16 @@ const TaskDetails: FC = ({ taskID }) => { })); } + function taskRequestHandle() { + if (!userData) { + return; + } + addOrUpdateTaskRequest({ taskId: taskID, userId: userData.id }) + .unwrap() + .then(() => toast(SUCCESS, 'Successfully requested for task')) + .catch((error) => toast(ERROR, error.data.message)); + } + function renderLoadingComponent() { if (isLoading) { return

Loading...

; @@ -317,6 +331,14 @@ const TaskDetails: FC = ({ taskID }) => { )} /> + + +