Skip to content

Commit 5ce6542

Browse files
[PRMP-1493] Implement restriction post and complete page
1 parent 4fd433a commit 5ce6542

File tree

10 files changed

+330
-101
lines changed

10 files changed

+330
-101
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,106 @@
11
import { render, screen } from '@testing-library/react';
22
import UserPatientRestrictionsAddConfirmStage from './UserPatientRestrictionsAddConfirmStage';
3+
import { Mock } from 'vitest';
4+
import usePatient from '../../../../helpers/hooks/usePatient';
5+
import postUserPatientRestriction from '../../../../helpers/requests/userPatientRestrictions/createUserPatientRestriction';
6+
import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
7+
import { userEvent } from '@testing-library/user-event';
8+
import { routeChildren, routes } from '../../../../types/generic/routes';
39

4-
vi.mock('react-router-dom');
10+
vi.mock('react-router-dom', async () => {
11+
const actual = await vi.importActual('react-router-dom');
12+
return {
13+
...actual,
14+
useNavigate: (): Mock => mockNavigate,
15+
Link: ({ children, to }: { children: React.ReactNode; to: string }): React.JSX.Element => (
16+
<a href={to}>{children}</a>
17+
),
18+
};
19+
});
520
vi.mock('../../../../helpers/hooks/usePatient');
21+
vi.mock('../../../../helpers/hooks/useBaseAPIUrl');
22+
vi.mock('../../../../helpers/hooks/useBaseAPIHeaders');
23+
vi.mock('../../../../helpers/requests/userPatientRestrictions/createUserPatientRestriction');
24+
25+
const mockNavigate = vi.fn();
26+
const mockUsePatient = usePatient as Mock;
27+
const mockPostUserPatientRestriction = postUserPatientRestriction as Mock;
28+
const mockPatient = buildPatientDetails();
29+
const userInformation = {
30+
name: 'John Doe',
31+
smartcardId: '123456789012',
32+
firstName: 'John',
33+
lastName: 'Doe',
34+
};
635

736
describe('UserPatientRestrictionsAddConfirmStage', () => {
8-
it('renders correctly', () => {
9-
const userInformation = {
10-
name: 'John Doe',
11-
smartcardId: '123456789012',
12-
firstName: 'John',
13-
lastName: 'Doe',
14-
};
37+
beforeEach(() => {
38+
vi.resetAllMocks();
39+
mockUsePatient.mockReturnValue(mockPatient);
40+
mockPostUserPatientRestriction.mockResolvedValue({});
41+
});
1542

43+
it('renders correctly', () => {
1644
render(<UserPatientRestrictionsAddConfirmStage userInformation={userInformation} />);
1745

1846
expect(screen.getByTestId('smartcard-id')).toHaveTextContent(userInformation.smartcardId);
1947
expect(screen.getByTestId('staff-member')).toHaveTextContent(
2048
`${userInformation.firstName} ${userInformation.lastName}`,
2149
);
2250
});
51+
52+
it('submits new patient restriction and navigates to success page on button click', async () => {
53+
render(<UserPatientRestrictionsAddConfirmStage userInformation={userInformation} />);
54+
55+
const button = screen.getByTestId('continue-button');
56+
await userEvent.click(button);
57+
58+
expect(mockPostUserPatientRestriction).toHaveBeenCalledWith(
59+
expect.objectContaining({
60+
nhsNumber: mockPatient.nhsNumber,
61+
smartcardId: userInformation.smartcardId,
62+
}),
63+
);
64+
65+
expect(mockNavigate).toHaveBeenCalledWith(
66+
routeChildren.USER_PATIENT_RESTRICTIONS_ACTION_COMPLETE,
67+
);
68+
});
69+
70+
it('shows loading state when submitting restriction', async () => {
71+
render(<UserPatientRestrictionsAddConfirmStage userInformation={userInformation} />);
72+
73+
mockPostUserPatientRestriction.mockReturnValue(new Promise(() => {}));
74+
75+
const button = screen.getByTestId('continue-button');
76+
await userEvent.click(button);
77+
78+
expect(screen.getByText('Processing...')).toBeInTheDocument();
79+
});
80+
81+
it('navigates to error page on 500', async () => {
82+
render(<UserPatientRestrictionsAddConfirmStage userInformation={userInformation} />);
83+
84+
mockPostUserPatientRestriction.mockRejectedValue({
85+
response: { status: 500 },
86+
});
87+
88+
const button = screen.getByTestId('continue-button');
89+
await userEvent.click(button);
90+
91+
expect(mockNavigate).toHaveBeenCalledWith(expect.stringContaining(routes.SERVER_ERROR));
92+
});
93+
94+
it('navigates to session expired page on 403', async () => {
95+
render(<UserPatientRestrictionsAddConfirmStage userInformation={userInformation} />);
96+
97+
mockPostUserPatientRestriction.mockRejectedValue({
98+
response: { status: 403 },
99+
});
100+
101+
const button = screen.getByTestId('continue-button');
102+
await userEvent.click(button);
103+
104+
expect(mockNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
105+
});
23106
});

app/src/components/blocks/_userPatientRestrictions/userPatientRestrictionsAddConfirmStage/UserPatientRestrictionsAddConfirmStage.tsx

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,63 @@
1-
import { Link } from 'react-router-dom';
1+
import { Link, useNavigate } from 'react-router-dom';
22
import useTitle from '../../../../helpers/hooks/useTitle';
33
import { UserInformation } from '../../../../types/generic/userPatientRestriction';
44
import BackButton from '../../../generic/backButton/BackButton';
55
import PatientSummary from '../../../generic/patientSummary/PatientSummary';
66
import StaffMemberDetails from '../../../generic/staffMemberDetails/StaffMemberDetails';
7-
import { routeChildren } from '../../../../types/generic/routes';
7+
import { routeChildren, routes } from '../../../../types/generic/routes';
88
import { Button } from 'nhsuk-react-components';
9+
import postUserPatientRestriction from '../../../../helpers/requests/userPatientRestrictions/createUserPatientRestriction';
10+
import usePatient from '../../../../helpers/hooks/usePatient';
11+
import useBaseAPIUrl from '../../../../helpers/hooks/useBaseAPIUrl';
12+
import useBaseAPIHeaders from '../../../../helpers/hooks/useBaseAPIHeaders';
13+
import { AxiosError } from 'axios';
14+
import { isMock } from '../../../../helpers/utils/isLocal';
15+
import { errorToParams } from '../../../../helpers/utils/errorToParams';
16+
import { useState } from 'react';
17+
import SpinnerButton from '../../../generic/spinnerButton/SpinnerButton';
918

1019
type Props = {
1120
userInformation: UserInformation;
1221
};
1322

1423
const UserPatientRestrictionsAddConfirmStage = ({ userInformation }: Props): React.JSX.Element => {
24+
const navigate = useNavigate();
25+
const patient = usePatient();
26+
const baseAPIUrl = useBaseAPIUrl();
27+
const baseAPIHeaders = useBaseAPIHeaders();
28+
1529
const pageTitle = 'Check the details of the restriction';
1630
useTitle({ pageTitle });
1731

18-
const addRestriction = (): void => {};
32+
const [creatingRestriction, setCreatingRestriction] = useState(false);
33+
34+
const addRestriction = async (): Promise<void> => {
35+
setCreatingRestriction(true);
36+
37+
try {
38+
await postUserPatientRestriction({
39+
nhsNumber: patient!.nhsNumber,
40+
smartcardId: userInformation.smartcardId,
41+
baseAPIUrl,
42+
baseAPIHeaders,
43+
});
44+
45+
handleSuccess();
46+
} catch (e) {
47+
const error = e as AxiosError;
48+
if (isMock(error)) {
49+
handleSuccess();
50+
} else if (error.response?.status === 403) {
51+
navigate(routes.SESSION_EXPIRED);
52+
} else {
53+
navigate(routes.SERVER_ERROR + errorToParams(error));
54+
}
55+
}
56+
};
57+
58+
const handleSuccess = (): void => {
59+
navigate(routeChildren.USER_PATIENT_RESTRICTIONS_ACTION_COMPLETE);
60+
};
1961

2062
return (
2163
<>
@@ -34,14 +76,18 @@ const UserPatientRestrictionsAddConfirmStage = ({ userInformation }: Props): Rea
3476

3577
<StaffMemberDetails userInformation={userInformation} />
3678

37-
<div className="action-button-group">
38-
<Button data-testid="continue-button" onClick={addRestriction}>
39-
Continue to add this restriction
40-
</Button>
41-
<Link className="ml-4" to={routeChildren.USER_PATIENT_RESTRICTIONS_ADD_CANCEL}>
42-
Cancel without adding a restriction
43-
</Link>
44-
</div>
79+
{creatingRestriction ? (
80+
<SpinnerButton id="creating-restriction-spinner" status="Processing..." />
81+
) : (
82+
<div className="action-button-group">
83+
<Button data-testid="continue-button" onClick={addRestriction}>
84+
Continue to add this restriction
85+
</Button>
86+
<Link className="ml-4" to={routeChildren.USER_PATIENT_RESTRICTIONS_ADD_CANCEL}>
87+
Cancel without adding a restriction
88+
</Link>
89+
</div>
90+
)}
4591
</>
4692
);
4793
};

app/src/components/blocks/_userPatientRestrictions/userPatientRestrictionsRemoveConfirmStage/UserPatientRestrictionsRemoveConfirmStage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('UserPatientRestrictionsRemoveConfirmStage', () => {
5959
nhsNumber: mockRestriction.nhsNumber,
6060
});
6161
expect(mockNavigate).toHaveBeenCalledWith(
62-
routeChildren.USER_PATIENT_RESTRICTIONS_REMOVE_COMPLETE,
62+
routeChildren.USER_PATIENT_RESTRICTIONS_ACTION_COMPLETE,
6363
);
6464
});
6565
});

app/src/components/blocks/_userPatientRestrictions/userPatientRestrictionsRemoveConfirmStage/UserPatientRestrictionsRemoveConfirmStage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const UserPatientRestrictionsRemoveConfirmStage = ({ restriction }: Props): Reac
5151
}
5252
}
5353

54-
navigate(routeChildren.USER_PATIENT_RESTRICTIONS_REMOVE_COMPLETE);
54+
navigate(routeChildren.USER_PATIENT_RESTRICTIONS_ACTION_COMPLETE);
5555
};
5656

5757
return (
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import axios from 'axios';
2+
import { Mocked } from 'vitest';
3+
import postUserPatientRestriction from './createUserPatientRestriction';
4+
import { endpoints } from '../../../types/generic/endpoints';
5+
6+
vi.mock('axios');
7+
const mockedAxios = axios as Mocked<typeof axios>;
8+
9+
describe('createUserPatientRestriction', () => {
10+
it('should make expected API call', async () => {
11+
const mockPost = vi.fn();
12+
mockedAxios.post.mockImplementation(mockPost);
13+
14+
const args = {
15+
smartcardId: '123',
16+
nhsNumber: '9876543210',
17+
baseAPIUrl: 'http://example.com/api',
18+
baseAPIHeaders: { Authorization: 'Bearer token', 'Content-Type': 'application/json' },
19+
};
20+
21+
await postUserPatientRestriction(args);
22+
23+
expect(mockPost).toHaveBeenCalledWith(
24+
`${args.baseAPIUrl}${endpoints.USER_PATIENT_RESTRICTIONS}`,
25+
{
26+
smartcardId: args.smartcardId,
27+
nhsNumber: args.nhsNumber,
28+
},
29+
{
30+
headers: {
31+
...args.baseAPIHeaders,
32+
},
33+
params: {
34+
patientId: args.nhsNumber,
35+
},
36+
},
37+
);
38+
});
39+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import axios, { AxiosError } from 'axios';
2+
import { AuthHeaders } from '../../../types/blocks/authHeaders';
3+
import { endpoints } from '../../../types/generic/endpoints';
4+
5+
type CreateUserPatientRestrictionArgs = {
6+
smartcardId: string;
7+
nhsNumber: string;
8+
baseAPIUrl: string;
9+
baseAPIHeaders: AuthHeaders;
10+
};
11+
12+
const postUserPatientRestriction = async ({
13+
smartcardId,
14+
nhsNumber,
15+
baseAPIUrl,
16+
baseAPIHeaders,
17+
}: CreateUserPatientRestrictionArgs): Promise<void> => {
18+
try {
19+
const url = baseAPIUrl + endpoints.USER_PATIENT_RESTRICTIONS;
20+
await axios.post(
21+
url,
22+
{
23+
smartcardId,
24+
nhsNumber,
25+
},
26+
{
27+
headers: {
28+
...baseAPIHeaders,
29+
},
30+
params: {
31+
patientId: nhsNumber,
32+
},
33+
},
34+
);
35+
} catch (e) {
36+
const error = e as AxiosError;
37+
throw error;
38+
}
39+
};
40+
41+
export default postUserPatientRestriction;

app/src/pages/userPatientRestrictionsPage/UserRestrictionsPage.test.tsx renamed to app/src/pages/userPatientRestrictionsPage/UserPatientRestrictionsPage.test.tsx

File renamed without changes.

0 commit comments

Comments
 (0)