Skip to content

Commit

Permalink
[ASAP-534] - submit quick check replies (#4429)
Browse files Browse the repository at this point in the history
* add discussions controller & data provider

* frontend changes

* resolve issues from rebase

* update manuscript data provider test

* code coverage

* added success alert

* fix test

* fix format

* use texteditor instead

* increase timeout on failing test

* update from review

* add max length to reply field

* update test

* increase createdby team limit
  • Loading branch information
AkosuaA authored Nov 4, 2024
1 parent c94935e commit 056790c
Show file tree
Hide file tree
Showing 54 changed files with 4,277 additions and 483 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Toast } from '@asap-hub/react-components';
import React, { createContext, useState } from 'react';

type FormType = 'manuscript' | 'compliance-report' | '';
type FormType = 'manuscript' | 'compliance-report' | 'quick-check' | '';

type ManuscriptToastContextData = {
setFormType: React.Dispatch<React.SetStateAction<FormType>>;
Expand All @@ -19,16 +19,17 @@ export const ManuscriptToastProvider = ({
const [formType, setFormType] = useState<FormType>('');

const formTypeMapping = {
manuscript: 'Manuscript',
'compliance-report': 'Compliance Report',
manuscript: 'Manuscript submitted successfully.',
'compliance-report': 'Compliance Report submitted successfully.',
'quick-check': 'Replied to quick check successfully.',
};

return (
<ManuscriptToastContext.Provider value={{ setFormType }}>
<>
{!!formType && (
<Toast accent="successLarge" onClose={() => setFormType('')}>
{formTypeMapping[formType]} submitted successfully.
{formTypeMapping[formType]}
</Toast>
)}
{children}
Expand Down
22 changes: 21 additions & 1 deletion apps/crn-frontend/src/network/teams/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ import {
TeamProfileWorkspace,
ToolModal,
} from '@asap-hub/react-components';
import { TeamTool, TeamResponse, ManuscriptPutRequest } from '@asap-hub/model';
import {
TeamTool,
TeamResponse,
ManuscriptPutRequest,
DiscussionPatchRequest,
} from '@asap-hub/model';
import { network, useRouteParams } from '@asap-hub/routing';
import { ToastContext } from '@asap-hub/react-context';

import {
useDiscussionById,
useIsComplianceReviewer,
usePatchTeamById,
usePutManuscript,
useReplyToDiscussion,
} from './state';
import { useEligibilityReason } from './useEligibilityReason';
import { useManuscriptToast } from './useManuscriptToast';

interface WorkspaceProps {
readonly team: TeamResponse & Required<Pick<TeamResponse, 'tools'>>;
Expand All @@ -28,8 +36,12 @@ const Workspace: React.FC<WorkspaceProps> = ({ team }) => {
const [deleting, setDeleting] = useState(false);
const patchTeam = usePatchTeamById(team.id);
const updateManuscript = usePutManuscript();
const replyToDiscussion = useReplyToDiscussion();
const getDiscussion = useDiscussionById;
const toast = useContext(ToastContext);

const { setFormType } = useManuscriptToast();

return (
<>
<Route path={path}>
Expand Down Expand Up @@ -61,6 +73,14 @@ const Workspace: React.FC<WorkspaceProps> = ({ team }) => {
}
}
isComplianceReviewer={isComplianceReviewer}
onReplyToDiscussion={async (
id: string,
patch: DiscussionPatchRequest,
) => {
await replyToDiscussion(id, patch);
setFormType('quick-check');
}}
getDiscussion={getDiscussion}
/>
</Route>
<Route exact path={path + route.tools.template}>
Expand Down
22 changes: 22 additions & 0 deletions apps/crn-frontend/src/network/teams/__mocks__/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {
createDiscussionResponse,
createListLabsResponse,
createListTeamResponse,
createMessage,
createResearchOutputResponse,
createTeamResponse,
} from '@asap-hub/fixtures';
import {
DiscussionPatchRequest,
DiscussionResponse,
ListLabsResponse,
ListTeamResponse,
TeamPatchRequest,
Expand Down Expand Up @@ -55,3 +59,21 @@ export const updateTeamResearchOutput: jest.Mocked<
export const getLabs = jest.fn(
async (): Promise<ListLabsResponse> => createListLabsResponse(5),
);

export const getDiscussion = jest.fn(
async (id: string): Promise<DiscussionResponse> => ({
...createDiscussionResponse(),
id,
}),
);

export const updateDiscussion = jest.fn(
async (
id: string,
patch: DiscussionPatchRequest,
): Promise<DiscussionResponse> => {
const discussion = await getDiscussion(id);
discussion.replies = [createMessage(patch.replyText)];
return discussion;
},
);
141 changes: 125 additions & 16 deletions apps/crn-frontend/src/network/teams/__tests__/Workspace.test.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
import { ReactNode, Suspense } from 'react';
import { RecoilRoot } from 'recoil';
import { MemoryRouter, Route } from 'react-router-dom';
import {
Auth0Provider,
WhenReady,
} from '@asap-hub/crn-frontend/src/auth/test-utils';
import { mockAlert } from '@asap-hub/dom-test-utils';
render,
waitFor,
getByText as getChildByText,
act,
fireEvent,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TeamResponse } from '@asap-hub/model';
import {
createDiscussionResponse,
createManuscriptResponse,
createMessage,
createTeamResponse,
} from '@asap-hub/fixtures';
import { enable } from '@asap-hub/flags';
import { TeamResponse } from '@asap-hub/model';
import { ToastContext } from '@asap-hub/react-context';
import { network } from '@asap-hub/routing';
import { ToastContext } from '@asap-hub/react-context';
import { mockAlert } from '@asap-hub/dom-test-utils';

import {
getByText as getChildByText,
render,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ReactNode, Suspense } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
Auth0Provider,
WhenReady,
} from '@asap-hub/crn-frontend/src/auth/test-utils';
import { enable } from '@asap-hub/flags';

import {
patchTeam,
updateManuscript,
getDiscussion,
updateDiscussion,
} from '../api';

import { patchTeam, updateManuscript } from '../api';
import Workspace from '../Workspace';
import { ManuscriptToastProvider } from '../ManuscriptToastProvider';

jest.setTimeout(30000);
jest.mock('../api', () => ({
patchTeam: jest.fn(),
updateManuscript: jest.fn().mockResolvedValue({}),
getDiscussion: jest.fn(),
updateDiscussion: jest.fn(),
}));

const mockPatchTeam = patchTeam as jest.MockedFunction<typeof patchTeam>;
const mockGetDiscussion = getDiscussion as jest.MockedFunction<
typeof getDiscussion
>;
const mockUpdateDiscussion = updateDiscussion as jest.MockedFunction<
typeof updateDiscussion
>;

const id = '42';

Expand Down Expand Up @@ -334,3 +355,91 @@ describe('the edit tool dialog', () => {
);
});
});

describe('manuscript quick check discussion', () => {
const manuscript = createManuscriptResponse();
manuscript.versions[0]!.acknowledgedGrantNumber = 'No';
const reply = createMessage('when can this be completed?');
const quickCheckResponse = 'We have not been able to complete this yet';
const acknowledgedGrantNumberDiscussion = createDiscussionResponse(
quickCheckResponse,
[reply],
);
manuscript.versions[0]!.acknowledgedGrantNumberDetails =
acknowledgedGrantNumberDiscussion;

it('fetches quick check discussion details', async () => {
enable('DISPLAY_MANUSCRIPTS');

mockGetDiscussion.mockResolvedValueOnce(acknowledgedGrantNumberDiscussion);
const { getByText, findByTestId, getByTestId } = renderWithWrapper(
<Workspace
team={{
...createTeamResponse(),
id,
manuscripts: [manuscript],
tools: [],
}}
/>,
);

await act(async () => {
userEvent.click(await findByTestId('collapsible-button'));
userEvent.click(getByTestId('version-collapsible-button'));
});

expect(getByText(quickCheckResponse)).toBeVisible();
expect(getByText(reply.text)).toBeVisible();
expect(mockGetDiscussion).toHaveBeenLastCalledWith(
acknowledgedGrantNumberDiscussion.id,
expect.anything(),
);
});

it('replies to a quick check discussion', async () => {
enable('DISPLAY_MANUSCRIPTS');
mockGetDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
mockUpdateDiscussion.mockResolvedValue(acknowledgedGrantNumberDiscussion);
const { findByTestId, getByRole, getByTestId } = renderWithWrapper(
<ManuscriptToastProvider>
<Workspace
team={{
...createTeamResponse(),
id,
manuscripts: [manuscript],
tools: [],
}}
/>
</ManuscriptToastProvider>,
);

await act(async () => {
userEvent.click(await findByTestId('collapsible-button'));
userEvent.click(getByTestId('version-collapsible-button'));
});

const replyButton = getByRole('button', { name: /Reply/i });
userEvent.click(replyButton);

const replyEditor = getByTestId('editor');
userEvent.click(replyEditor);
userEvent.tab();
fireEvent.input(replyEditor, { data: 'new reply' });
userEvent.tab();

const sendButton = getByRole('button', { name: /Send/i });

await waitFor(() => {
expect(sendButton).toBeEnabled();
});
await act(async () => {
userEvent.click(sendButton);
});

expect(mockUpdateDiscussion).toHaveBeenLastCalledWith(
acknowledgedGrantNumberDiscussion.id,
{ replyText: 'new reply' },
expect.anything(),
);
});
});
77 changes: 77 additions & 0 deletions apps/crn-frontend/src/network/teams/__tests__/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {
createDiscussionResponse,
createListLabsResponse,
createListTeamResponse,
createManuscriptResponse,
createTeamResponse,
createMessage,
} from '@asap-hub/fixtures';
import { GetListOptions } from '@asap-hub/frontend-utils';
import {
ComplianceReportPostRequest,
DiscussionResponse,
ManuscriptFileResponse,
ManuscriptPostRequest,
ManuscriptPutRequest,
Expand All @@ -21,12 +24,14 @@ import {
createComplianceReport,
createManuscript,
createResearchOutput,
getDiscussion,
getLabs,
getManuscript,
getTeam,
getTeams,
patchTeam,
updateManuscript,
updateDiscussion,
updateTeamResearchOutput,
uploadManuscriptFile,
} from '../api';
Expand Down Expand Up @@ -475,3 +480,75 @@ describe('Compliance Report', () => {
});
});
});

describe('Discussion', () => {
describe('getDiscussion', () => {
it('makes an authorized GET request for the discussion id', async () => {
nock(API_BASE_URL, { reqheaders: { authorization: 'Bearer x' } })
.get('/discussions/42')
.reply(200, {});
await getDiscussion('42', 'Bearer x');
expect(nock.isDone()).toBe(true);
});

it('returns a successfully fetched discussion', async () => {
const discussion = createDiscussionResponse();
nock(API_BASE_URL).get('/discussions/42').reply(200, discussion);
expect(await getDiscussion('42', '')).toEqual(discussion);
});

it('returns undefined for a 404', async () => {
nock(API_BASE_URL).get('/discussions/42').reply(404);
expect(await getDiscussion('42', '')).toBe(undefined);
});

it('errors for another status', async () => {
nock(API_BASE_URL).get('/discussions/42').reply(500);
await expect(
getDiscussion('42', ''),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Failed to fetch discussion with id 42. Expected status 2xx or 404. Received status 500."`,
);
});
});

describe('updateDiscussion', () => {
const patch = {
replyText: 'test reply',
};
it('makes an authorized PATCH request for the discussion id', async () => {
nock(API_BASE_URL, { reqheaders: { authorization: 'Bearer x' } })
.patch('/discussions/42')
.reply(200, {});

await updateDiscussion('42', patch, 'Bearer x');
expect(nock.isDone()).toBe(true);
});

it('passes the patch object in the body', async () => {
nock(API_BASE_URL).patch('/discussions/42', patch).reply(200, {});

await updateDiscussion('42', patch, '');
expect(nock.isDone()).toBe(true);
});

it('returns a successfully updated discussion', async () => {
const updated: Partial<DiscussionResponse> = {
replies: [createMessage('')],
};
nock(API_BASE_URL).patch('/discussions/42', patch).reply(200, updated);

expect(await updateDiscussion('42', patch, '')).toEqual(updated);
});

it('errors for an error status', async () => {
nock(API_BASE_URL).patch('/discussions/42', patch).reply(500, {});

await expect(
updateDiscussion('42', patch, ''),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Failed to update discussion with id 42. Expected status 200. Received status 500."`,
);
});
});
});
Loading

0 comments on commit 056790c

Please sign in to comment.