From fc0d64ce1008becbda71f1e2df742279d154c70d Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Mon, 24 Jul 2023 05:52:13 +0530 Subject: [PATCH 1/4] feat: Add button to request for task --- __mocks__/handlers.ts | 2 + __mocks__/handlers/task-request.handler.ts | 18 ++++ .../Components/Tasks/TaskDetails.test.tsx | 84 ++++++++++++++++++- src/app/services/api.ts | 2 + src/app/services/taskRequestApi.ts | 21 +++++ src/components/taskDetails/index.tsx | 22 +++++ 6 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 __mocks__/handlers/task-request.handler.ts create mode 100644 src/app/services/taskRequestApi.ts 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..a64980bc1 --- /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(204)); + }), +]; + +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 26308335d..762234987 100644 --- a/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx +++ b/__tests__/Unit/Components/Tasks/TaskDetails.test.tsx @@ -12,7 +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 { mockGetTaskProgress } from '__mocks__/db/progresses'; +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', @@ -226,6 +227,8 @@ describe('TaskDetails Page', () => { }); test('should update the title and description with the new values', async () => { + server.use(...taskDetailsHandler); + renderWithRouter( @@ -364,6 +367,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/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..2630ba04b --- /dev/null +++ b/src/app/services/taskRequestApi.ts @@ -0,0 +1,21 @@ +import { api } from './api'; + +type AddOrUpdateMutation = { + taskId: string; + userId: string; +}; + +export const taskRequestApi = api.injectEndpoints({ + endpoints: (build) => ({ + addOrUpdate: build.mutation({ + query: (body: AddOrUpdateMutation) => ({ + 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 46b04fd9a..8aa0e9bc0 100644 --- a/src/components/taskDetails/index.tsx +++ b/src/components/taskDetails/index.tsx @@ -26,6 +26,7 @@ import { parseDependencyValue } from '@/utils/parseDependency'; 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; @@ -87,6 +88,9 @@ const TaskDetails: FC = ({ taskID }) => { taskDetails?.dependsOn || [] ); + const [addOrUpdateTaskRequest, taskRequestUpdateStatus] = + useAddOrUpdateMutation(); + useEffect(() => { const fetchedData = data?.taskData; setTaskDetails(taskDetailsData); @@ -128,6 +132,16 @@ const TaskDetails: FC = ({ taskID }) => { setTaskDetails(initialDataRef.current); } + 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...

; @@ -326,6 +340,14 @@ const TaskDetails: FC = ({ taskID }) => { )} /> + + + From 1e397ec5b09762a2c0b2227c34f765670f908d3e Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Mon, 31 Jul 2023 01:12:44 +0530 Subject: [PATCH 2/4] fix: Review comments Add test for api --- __mocks__/handlers/task-request.handler.ts | 2 +- __tests__/Unit/hooks/taskRequestApi.tsx | 76 ++++++++++++++++++++++ src/app/services/taskRequestApi.ts | 13 +++- src/components/taskDetails/index.tsx | 4 +- 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 __tests__/Unit/hooks/taskRequestApi.tsx diff --git a/__mocks__/handlers/task-request.handler.ts b/__mocks__/handlers/task-request.handler.ts index a64980bc1..955595ffe 100644 --- a/__mocks__/handlers/task-request.handler.ts +++ b/__mocks__/handlers/task-request.handler.ts @@ -4,7 +4,7 @@ const URL = process.env.NEXT_PUBLIC_BASE_URL; export const taskRequestSuccessHandlers = [ rest.post(`${URL}/taskRequests/addOrUpdate`, (_, res, ctx) => { - return res(ctx.status(204)); + return res(ctx.status(201), ctx.json({ message: 'Task request successfully created' })); }), ]; diff --git a/__tests__/Unit/hooks/taskRequestApi.tsx b/__tests__/Unit/hooks/taskRequestApi.tsx new file mode 100644 index 000000000..fc5dbe1cf --- /dev/null +++ b/__tests__/Unit/hooks/taskRequestApi.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 update 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/taskRequestApi.ts b/src/app/services/taskRequestApi.ts index 2630ba04b..6cd4b6074 100644 --- a/src/app/services/taskRequestApi.ts +++ b/src/app/services/taskRequestApi.ts @@ -1,14 +1,21 @@ import { api } from './api'; -type AddOrUpdateMutation = { +type AddOrUpdateMutationQuery = { taskId: string; userId: string; }; +type AddOrUpdateMutationQueryRes = + | { message: string } + | { message: string; requestors: string[] }; + export const taskRequestApi = api.injectEndpoints({ endpoints: (build) => ({ - addOrUpdate: build.mutation({ - query: (body: AddOrUpdateMutation) => ({ + addOrUpdate: build.mutation< + AddOrUpdateMutationQueryRes, + AddOrUpdateMutationQuery + >({ + query: (body) => ({ url: 'taskRequests/addOrUpdate', method: 'POST', body, diff --git a/src/components/taskDetails/index.tsx b/src/components/taskDetails/index.tsx index 9b2814970..fbe32dfc1 100644 --- a/src/components/taskDetails/index.tsx +++ b/src/components/taskDetails/index.tsx @@ -64,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 } = @@ -124,7 +124,7 @@ const TaskDetails: FC = ({ taskID }) => { if (!userData) { return; } - addOrUpdateTaskRequest({ taskId: taskID, userId: userData?.id }) + addOrUpdateTaskRequest({ taskId: taskID, userId: userData.id }) .unwrap() .then(() => toast(SUCCESS, 'Successfully requested for task')) .catch((error) => toast(ERROR, error.data.message)); From 78ef81d730a06ea7aa68415507519d423c5da8b3 Mon Sep 17 00:00:00 2001 From: Tanishq Singla Date: Mon, 31 Jul 2023 10:03:06 +0530 Subject: [PATCH 3/4] Rename taskRequestApi.tsx to taskRequestApi.test.tsx --- .../Unit/hooks/{taskRequestApi.tsx => taskRequestApi.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename __tests__/Unit/hooks/{taskRequestApi.tsx => taskRequestApi.test.tsx} (100%) diff --git a/__tests__/Unit/hooks/taskRequestApi.tsx b/__tests__/Unit/hooks/taskRequestApi.test.tsx similarity index 100% rename from __tests__/Unit/hooks/taskRequestApi.tsx rename to __tests__/Unit/hooks/taskRequestApi.test.tsx From 9fd9f790773ad2f064832c51f0b12089bb8bcf66 Mon Sep 17 00:00:00 2001 From: TanishqSingla Date: Wed, 2 Aug 2023 21:44:12 +0530 Subject: [PATCH 4/4] fix: same test names --- __tests__/Unit/hooks/taskRequestApi.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/Unit/hooks/taskRequestApi.test.tsx b/__tests__/Unit/hooks/taskRequestApi.test.tsx index fc5dbe1cf..eb9f871c0 100644 --- a/__tests__/Unit/hooks/taskRequestApi.test.tsx +++ b/__tests__/Unit/hooks/taskRequestApi.test.tsx @@ -48,7 +48,7 @@ describe('useAddOrUpdateMutation', () => { expect(response.isError).toBe(false); }); - it('should update the user', async () => { + it('should return error while updating the user', async () => { server.use(...taskRequestErrorHandler); const { result, waitForNextUpdate } = renderHook( () => useAddOrUpdateMutation(),