Skip to content

Commit

Permalink
refactor output form tests (#4190)
Browse files Browse the repository at this point in the history
* refactor output form modal

* linting

* linting

* Update packages/gp2-components/src/organisms/__tests__/ConfirmAndSaveOutput.test.tsx

Co-authored-by: Gabriela Ueno <[email protected]>

---------

Co-authored-by: Gabriela Ueno <[email protected]>
  • Loading branch information
lctrt and gabiayako authored Mar 11, 2024
1 parent d35c760 commit dee9144
Show file tree
Hide file tree
Showing 5 changed files with 711 additions and 968 deletions.
163 changes: 163 additions & 0 deletions packages/gp2-components/src/organisms/ConfirmAndSaveOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { ReactNode, useState } from 'react';
import { ConfirmModal as Modal, Link } from '@asap-hub/react-components';
import {
INVITE_SUPPORT_EMAIL,
mailToSupport,
} from '@asap-hub/react-components/src/mail';
import { gp2 } from '@asap-hub/model';
import { useNotificationContext } from '@asap-hub/react-context';

import { EntityMappper } from '../templates/CreateOutputPage';
import { GetWrappedOnSave } from './Form';

const capitalizeFirstLetter = (string: string) =>
string.charAt(0).toUpperCase() + string.slice(1);
export type ConfirmAndSaveOutputProps = {
children: (state: {
save: () => Promise<gp2.OutputResponse | void>;
}) => ReactNode;
getWrappedOnSave: GetWrappedOnSave<gp2.OutputResponse>;
setRedirectOnSave: (url: string) => void;
path: (id: string) => string;
documentType: gp2.OutputDocumentType;
title: string | undefined;
currentPayload: gp2.OutputPostRequest;
isEditing: boolean;
createVersion: boolean;
shareOutput: (
payload: gp2.OutputPostRequest,
) => Promise<gp2.OutputResponse | void>;
entityType: 'workingGroup' | 'project';
};

const getBannerMessage = (
entityType: 'workingGroup' | 'project',
documentType: gp2.OutputDocumentType,
published: boolean,
createVersion: boolean,
) =>
`${createVersion ? 'New ' : ''}${EntityMappper[entityType]} ${documentType} ${
createVersion ? 'version ' : ''
}${published || createVersion ? 'published' : 'saved'} successfully.`;

export const ConfirmAndSaveOutput = ({
children,
getWrappedOnSave,
setRedirectOnSave,
path,
documentType,
title,
shareOutput,
currentPayload,
createVersion,
isEditing,
entityType,
}: ConfirmAndSaveOutputProps) => {
const [displayPublishModal, setDisplayPublishModal] = useState(false);
const [displayVersionModal, setDiplayVersionModal] = useState(false);

const { addNotification, removeNotification, notifications } =
useNotificationContext();

const setBannerMessage = (
message: string,
page: 'output' | 'output-form',
bannerType: 'error' | 'success',
) => {
if (
notifications[0] &&
notifications[0]?.message !== capitalizeFirstLetter(message)
) {
removeNotification(notifications[0]);
}
addNotification({
message: capitalizeFirstLetter(message),
page,
type: bannerType,
});
};
const save = async (skipConfirmationModal: boolean = false) => {
const displayModalFn =
!isEditing && !skipConfirmationModal
? () => {
setDisplayPublishModal(true);
}
: createVersion && !skipConfirmationModal
? () => {
setDiplayVersionModal(true);
}
: null;

const output = await getWrappedOnSave(
() => shareOutput(currentPayload),
(error) => setBannerMessage(error, 'output-form', 'error'),
displayModalFn,
)();

if (output && typeof output.id === 'string') {
setBannerMessage(
getBannerMessage(entityType, documentType, !title, createVersion),
'output',
'success',
);
setRedirectOnSave(path(output.id));
}
return output;
};
return (
<>
{displayPublishModal && (
<Modal
title="Publish output for the whole hub?"
cancelText="Cancel"
onCancel={() => setDisplayPublishModal(false)}
confirmText="Publish Output"
onSave={async () => {
const skipPublishModal = true;
const result = await save(skipPublishModal);
if (!result) {
setDisplayPublishModal(false);
}
}}
description={
<>
All {entityType === 'workingGroup' ? 'working group' : 'project'}{' '}
members listed on this output will be notified and all GP2 members
will be able to access it. If you need to unpublish this output,
please contact{' '}
{<Link href={mailToSupport()}>{INVITE_SUPPORT_EMAIL}</Link>}.
</>
}
/>
)}

{displayVersionModal && (
<Modal
title="Publish new version for the whole hub?"
cancelText="Cancel"
onCancel={() => setDiplayVersionModal(false)}
confirmText="Publish new version"
onSave={async () => {
const skipPublishModal = true;
const result = await save(skipPublishModal);
if (!result) {
setDiplayVersionModal(false);
}
}}
description={
<>
All working group members listed on this output will be notified
and all GP2 members will be able to access it. If you want to add
or edit older versions after this new version was published,
please contact{' '}
{<Link href={mailToSupport()}>{INVITE_SUPPORT_EMAIL}</Link>}.
</>
}
/>
)}
{children({
save,
})}
</>
);
};
12 changes: 7 additions & 5 deletions packages/gp2-components/src/organisms/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ const styles = css({

export type FormStatus = 'initial' | 'isSaving' | 'hasError' | 'hasSaved';

export type GetWrappedOnSave<T> = (
onSaveFunction: () => Promise<T | void>,
addNotification: (error: string) => void,
onDisplayModal: (() => void) | null,
) => () => Promise<T | void>;

type FormProps<T> = {
validate?: () => boolean;
dirty: boolean; // mandatory so that it cannot be forgotten
serverErrors?: ValidationErrorResponse['data'];
children: (state: {
isSaving: boolean;
setRedirectOnSave: (url: string) => void;
getWrappedOnSave: (
onSaveFunction: () => Promise<T | void>,
addNotification: (error: string) => void,
onDisplayModal: (() => void) | null,
) => () => Promise<T | void>;
getWrappedOnSave: GetWrappedOnSave<T>;
onCancel: () => void;
}) => ReactNode;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { createOutputResponse } from '@asap-hub/fixtures/src/gp2';
import { OutputResponse } from '@asap-hub/model/src/gp2';
import { Button } from '@asap-hub/react-components';
import { NotificationContext } from '@asap-hub/react-context';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';

import {
ConfirmAndSaveOutput,
ConfirmAndSaveOutputProps,
} from '../ConfirmAndSaveOutput';
import Form, { GetWrappedOnSave } from '../Form';

describe('ConfirmAndSaveOutput', () => {
const addNotification = jest.fn();
const history = createMemoryHistory();
const shareOutput = jest.fn();

const wrapper: React.ComponentType = ({ children }) => (
<NotificationContext.Provider
value={{
notifications: [],
addNotification,
removeNotification: jest.fn(),
}}
>
<Router history={history}>{children}</Router>
</NotificationContext.Provider>
);

const renderElement = (props?: Partial<ConfirmAndSaveOutputProps>) =>
render(
<Form dirty={false}>
{({ getWrappedOnSave }) => (
<ConfirmAndSaveOutput
path={(id: string) => id}
documentType="Article"
title="title"
currentPayload={{
...createOutputResponse(),
tagIds: [],
contributingCohortIds: [],
mainEntityId: '',
relatedOutputIds: [],
relatedEventIds: [],
authors: [],
}}
shareOutput={shareOutput}
setRedirectOnSave={(url: string) => {}}
entityType="project"
isEditing={false}
createVersion={false}
getWrappedOnSave={
getWrappedOnSave as unknown as GetWrappedOnSave<OutputResponse>
}
{...props}
>
{({ save }) => <Button onClick={save}>Publish</Button>}
</ConfirmAndSaveOutput>
)}
</Form>,
{ wrapper },
);

describe('cancel', () => {
it('closes the publish modal when user clicks on cancel', async () => {
renderElement();
userEvent.click(screen.getByRole('button', { name: 'Publish' }));

expect(
screen.getByText('Publish output for the whole hub?'),
).toBeVisible();

userEvent.click(
within(screen.getByRole('dialog')).getByRole('button', {
name: 'Cancel',
}),
);

await waitFor(() => {
expect(
screen.queryByText('Publish output for the whole hub?'),
).not.toBeInTheDocument();
});
});

it('closes the version modal when user clicks on cancel', async () => {
renderElement({ isEditing: true, createVersion: true });

userEvent.click(screen.getByRole('button', { name: 'Publish' }));

expect(
screen.getByText('Publish new version for the whole hub?'),
).toBeVisible();

userEvent.click(
within(screen.getByRole('dialog')).getByRole('button', {
name: 'Cancel',
}),
);

await waitFor(() => {
expect(
screen.queryByText('Publish new version for the whole hub?'),
).not.toBeInTheDocument();
});
});
});
describe('server side errors', () => {
it('closes the publish modal when user clicks on save and there are server side errors', async () => {
shareOutput.mockRejectedValueOnce(new Error('something went wrong'));
renderElement();

userEvent.click(screen.getByRole('button', { name: 'Publish' }));

expect(
screen.getByText('Publish output for the whole hub?'),
).toBeVisible();

userEvent.click(screen.getByRole('button', { name: /Publish output/i }));

await waitFor(() => {
expect(
screen.queryByText('Publish output for the whole hub?'),
).not.toBeInTheDocument();
});
});
it('closes the version modal when user clicks on save and there are server side errors', async () => {
shareOutput.mockRejectedValueOnce(new Error('something went wrong'));
renderElement({ isEditing: true, createVersion: true });

userEvent.click(screen.getByRole('button', { name: 'Publish' }));

expect(
screen.getByText('Publish new version for the whole hub?'),
).toBeVisible();

userEvent.click(
screen.getByRole('button', { name: /Publish new version/i }),
);

await waitFor(() => {
expect(
screen.queryByText('Publish new version for the whole hub?'),
).not.toBeInTheDocument();
});
});
});
});
Loading

0 comments on commit dee9144

Please sign in to comment.