Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EASI-4667 Side Panel Routing / State management #2901

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/hooks/useDiscussionParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useHistory, useLocation } from 'react-router-dom';

const discussionModeKeys = ['view', 'start', 'reply'] as const;

export type DiscussionMode = (typeof discussionModeKeys)[number];

/**
* Handle Discussion (side panel modal) state with the url query params
* `discussionMode` and `discussionId`.
*/
export default function useDiscussionParams() {
const history = useHistory();
const location = useLocation();

return {
getDiscussionParams(): {
/** Undefined implies a closed modal */
discussionMode: DiscussionMode | undefined;
discussionId: string | undefined;
} {
const q = new URLSearchParams(location.search);

const discussionMode = q.get('discussionMode') as DiscussionMode | null;

// Silent ignore on invalid `discussionModeKeys`
if (
discussionMode === null ||
!discussionModeKeys.includes(discussionMode)
) {
return {
discussionMode: undefined,
discussionId: undefined
};
}

// Check reply mode for valid `discussionId`
// Silent fail if `discussionId` is invalid
if (discussionMode === 'reply') {
const discussionId = q.get('discussionId');

if (discussionId === null)
return {
discussionMode: undefined,
discussionId: undefined
};

return { discussionMode, discussionId };
}

return { discussionMode, discussionId: undefined };
},

/** Push a new url query to update the Discussion subviews state. `false` implies closing the modal */
pushDiscussionQuery(
query:
| { discussionMode: Extract<DiscussionMode, 'view' | 'start'> }
| {
discussionMode: Extract<DiscussionMode, 'reply'>;
discussionId: string;
}
| false
) {
if (query === false) {
history.push(`${location.pathname}`);
return;
}

const querystring = new URLSearchParams(query);
history.push(`${location.pathname}?${querystring}`);
}
};
}
33 changes: 19 additions & 14 deletions src/views/DiscussionBoard/Discussion.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
Expand All @@ -22,13 +23,15 @@ describe('Discussion component', () => {
const [discussion] = discussions;

render(
<VerboseMockedProvider>
<Discussion
discussion={discussion}
closeModal={vi.fn()}
setDiscussionAlert={vi.fn()}
/>
</VerboseMockedProvider>
<MemoryRouter>
<VerboseMockedProvider>
<Discussion
discussion={discussion}
closeModal={vi.fn()}
setDiscussionAlert={vi.fn()}
/>
</VerboseMockedProvider>
</MemoryRouter>
);

expect(
Expand Down Expand Up @@ -105,13 +108,15 @@ describe('Discussion component', () => {
};

render(
<VerboseMockedProvider>
<Discussion
discussion={discussion}
closeModal={vi.fn()}
setDiscussionAlert={vi.fn()}
/>
</VerboseMockedProvider>
<MemoryRouter>
<VerboseMockedProvider>
<Discussion
discussion={discussion}
closeModal={vi.fn()}
setDiscussionAlert={vi.fn()}
/>
</VerboseMockedProvider>
</MemoryRouter>
);

// Toggle view more replies
Expand Down
3 changes: 0 additions & 3 deletions src/views/DiscussionBoard/Discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ const Discussion = ({
return (
<div>
<h1 className="margin-bottom-5">{t('general.discussion')}</h1>

<DiscussionPost {...initialPost} />

{replies.length > 0 && (
<>
<div className="display-flex flex-justify">
Expand Down Expand Up @@ -74,7 +72,6 @@ const Discussion = ({
)}
</>
)}

<h2 className="margin-bottom-2 margin-top-8">{t('general.reply')}</h2>
<DiscussionForm
type="reply"
Expand Down
19 changes: 16 additions & 3 deletions src/views/DiscussionBoard/ViewDiscussions.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import i18next from 'i18next';
Expand All @@ -20,7 +21,11 @@ describe('ViewDiscussions component', () => {
);

it('renders the component', () => {
render(<ViewDiscussions grbDiscussions={mockDiscussions()} />);
render(
<MemoryRouter>
<ViewDiscussions grbDiscussions={mockDiscussions()} />
</MemoryRouter>
);

expect(
screen.getByRole('heading', {
Expand All @@ -41,7 +46,11 @@ describe('ViewDiscussions component', () => {
});

it('renders alerts for no discussion posts', () => {
render(<ViewDiscussions grbDiscussions={[]} />);
render(
<MemoryRouter>
<ViewDiscussions grbDiscussions={[]} />
</MemoryRouter>
);

expect(screen.getByText(noNewDiscussionsText)).toBeInTheDocument();

Expand All @@ -61,7 +70,11 @@ describe('ViewDiscussions component', () => {
discussion => discussion.replies.length === 0
);

render(<ViewDiscussions grbDiscussions={grbDiscussions} />);
render(
<MemoryRouter>
<ViewDiscussions grbDiscussions={grbDiscussions} />
</MemoryRouter>
);

/* Discussions with replies */

Expand Down
22 changes: 8 additions & 14 deletions src/views/DiscussionBoard/ViewDiscussions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SystemIntakeGRBReviewDiscussionFragment } from 'gql/gen/graphql';

import Alert from 'components/shared/Alert';
import IconButton from 'components/shared/IconButton';
import useDiscussionParams from 'hooks/useDiscussionParams';

import DiscussionPost from './components/DiscussionPost';
import DiscussionsList from './components/DiscussionsList';
Expand All @@ -22,6 +23,8 @@ type ViewDiscussionsProps = {
const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => {
const { t } = useTranslation('discussions');

const { pushDiscussionQuery } = useDiscussionParams();

const discussionsWithoutReplies: SystemIntakeGRBReviewDiscussionFragment[] =
grbDiscussions.filter(discussion => discussion.replies.length === 0);

Expand All @@ -36,18 +39,17 @@ const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => {
<p className="font-body-lg text-light line-height-body-5 margin-top-105">
{t('governanceReviewBoard.internal.description')}
</p>

<h2 className="margin-top-5 margin-bottom-2">{t('general.label')}</h2>
<IconButton
type="button"
// TODO: Go to start discussion view
onClick={() => null}
onClick={() => {
pushDiscussionQuery({ discussionMode: 'start' });
}}
icon={<Icon.Announcement />}
unstyled
>
{t('general.startNewDiscussion')}
</IconButton>

<Accordion
className="discussions-list margin-top-5"
multiselectable
Expand All @@ -65,11 +67,7 @@ const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => {
<DiscussionsList type="discussions" initialCount={3}>
{discussionsWithoutReplies.map((discussion, index) => (
<li
/**
* TODO: Replace `index` key with `discussion.initialPost.id`
* Seeded data discussions all have same ID
*/
key={index} // eslint-disable-line react/no-array-index-key
key={discussion.initialPost.id}
className="padding-y-3 padding-x-205"
>
<DiscussionPost
Expand Down Expand Up @@ -99,11 +97,7 @@ const ViewDiscussions = ({ grbDiscussions }: ViewDiscussionsProps) => {
<DiscussionsList type="discussions" initialCount={3}>
{discussionsWithReplies.map((discussion, index) => (
<li
/**
* TODO: Replace `index` key with `discussion.initialPost.id`
* Seeded data discussions all have same ID
*/
key={index} // eslint-disable-line react/no-array-index-key
key={discussion.initialPost.id}
className="padding-y-3 padding-x-205"
>
<DiscussionPost
Expand Down
14 changes: 11 additions & 3 deletions src/views/DiscussionBoard/components/DiscussionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import FieldErrorMsg from 'components/shared/FieldErrorMsg';
import HelpText from 'components/shared/HelpText';
import Label from 'components/shared/Label';
import RequiredAsterisk from 'components/shared/RequiredAsterisk';
import useDiscussionParams from 'hooks/useDiscussionParams';
import { DiscussionAlert } from 'types/discussions';
import discussionSchema from 'validations/discussionSchema';

Expand Down Expand Up @@ -62,6 +63,8 @@ const DiscussionForm = ({
resolver: yupResolver(discussionSchema)
});

const { pushDiscussionQuery } = useDiscussionParams();

const createDiscussion = handleSubmit(({ content }) => {
if ('systemIntakeID' in mutationProps) {
mutateDiscussion({
Expand All @@ -83,9 +86,10 @@ const DiscussionForm = ({
message: t('general.alerts.startDiscussionError'),
type: 'error'
});
})
.finally(() => {
pushDiscussionQuery({ discussionMode: 'view' });
});

// TODO: Go back to discussion board view
}
});

Expand Down Expand Up @@ -159,7 +163,11 @@ const DiscussionForm = ({
</FormGroup>

<ButtonGroup>
<Button type="button" outline onClick={closeModal}>
<Button
type="button"
outline
onClick={() => pushDiscussionQuery({ discussionMode: 'view' })}
>
{t('general.cancel')}
</Button>
<Button type="submit" disabled={!isValid}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { SystemIntakeGRBReviewDiscussionFragment } from 'gql/gen/graphql';
import i18next from 'i18next';
Expand All @@ -14,10 +15,12 @@ const { initialPost, replies } = discussion;
describe('DiscussionPost', () => {
it('renders a discussion post with replies', () => {
render(
<DiscussionPost
{...discussion.initialPost}
replies={discussion.replies}
/>
<MemoryRouter>
<DiscussionPost
{...discussion.initialPost}
replies={discussion.replies}
/>
</MemoryRouter>
);

const {
Expand Down Expand Up @@ -50,15 +53,23 @@ describe('DiscussionPost', () => {
});

it('renders a discussion post without replies', () => {
render(<DiscussionPost {...discussion.initialPost} replies={[]} />);
render(
<MemoryRouter>
<DiscussionPost {...discussion.initialPost} replies={[]} />
</MemoryRouter>
);

expect(screen.getByRole('button', { name: 'Reply' })).toBeInTheDocument();

expect(screen.queryByTestId('lastReplyAtText')).toBeNull();
});

it('hides discussion reply data', () => {
render(<DiscussionPost {...discussion.initialPost} />);
render(
<MemoryRouter>
<DiscussionPost {...discussion.initialPost} />
</MemoryRouter>
);

expect(screen.queryByTestId('discussionReplies')).toBeNull();
});
Expand All @@ -74,10 +85,12 @@ describe('DiscussionPost', () => {
};

render(
<DiscussionPost
{...discussionNoRole.initialPost}
replies={discussionNoRole.replies}
/>
<MemoryRouter>
<DiscussionPost
{...discussionNoRole.initialPost}
replies={discussionNoRole.replies}
/>
</MemoryRouter>
);

expect(screen.getByText('Governance Admin Team')).toBeInTheDocument();
Expand Down
Loading
Loading