@openmrs/esm-fast-data-entry-app 1.4.2-pre.704 → 1.4.2-pre.707
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__mocks__/react-i18next.js +16 -4
- package/dist/1003.js +1 -1
- package/dist/1003.js.map +1 -1
- package/dist/{1252.js → 2355.js} +1 -1
- package/dist/2355.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +37 -37
- package/dist/openmrs-esm-fast-data-entry-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/e2e/core/test.ts +1 -1
- package/e2e/fixtures/api.ts +1 -1
- package/e2e/pages/sample-test.ts +1 -1
- package/e2e/specs/form-workflow.spec.ts +160 -0
- package/package.json +2 -2
- package/src/FormBootstrap.test.tsx +112 -0
- package/src/context/FormWorkflowReducer.test.ts +142 -0
- package/src/context/GroupFormWorkflowReducer.test.ts +302 -0
- package/src/context/GroupFormWorkflowReducer.ts +1 -1
- package/src/declarations.d.ts +0 -1
- package/src/group-form-entry-workflow/GroupSessionWorkspace.test.tsx +190 -0
- package/src/group-form-entry-workflow/SessionMetaWorkspace.test.tsx +94 -0
- package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.test.tsx +121 -0
- package/src/hooks/useGetPatient.test.tsx +113 -0
- package/tsconfig.json +1 -1
- package/dist/1252.js.map +0 -1
- package/e2e/specs/sample-test.spec.ts +0 -10
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { getGlobalStore, useConfig, useSession, useStore } from '@openmrs/esm-framework';
|
|
5
|
+
import FormBootstrap from '../FormBootstrap';
|
|
6
|
+
import GroupFormWorkflowContext from '../context/GroupFormWorkflowContext';
|
|
7
|
+
import GroupSessionWorkspace from './GroupSessionWorkspace';
|
|
8
|
+
|
|
9
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
10
|
+
getGlobalStore: jest.fn(),
|
|
11
|
+
useConfig: jest.fn(),
|
|
12
|
+
useSession: jest.fn(),
|
|
13
|
+
useStore: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('uuid', () => ({
|
|
17
|
+
v4: jest.fn(() => 'generated-visit-uuid'),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.mock('../FormBootstrap', () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: jest.fn(() => <div data-testid="form-bootstrap" />),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock('../patient-card/PatientCard', () => ({
|
|
26
|
+
__esModule: true,
|
|
27
|
+
default: ({ patientUuid, editEncounter }) => (
|
|
28
|
+
<button data-testid={`patient-card-${patientUuid}`} onClick={() => editEncounter(patientUuid)}>
|
|
29
|
+
{patientUuid}
|
|
30
|
+
</button>
|
|
31
|
+
),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('../CancelModal', () => ({
|
|
35
|
+
__esModule: true,
|
|
36
|
+
default: () => null,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
jest.mock('../CompleteModal', () => ({
|
|
40
|
+
__esModule: true,
|
|
41
|
+
default: () => null,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const mockGetGlobalStore = jest.mocked(getGlobalStore);
|
|
45
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
46
|
+
const mockUseSession = jest.mocked(useSession);
|
|
47
|
+
const mockUseStore = jest.mocked(useStore);
|
|
48
|
+
const mockFormBootstrap = FormBootstrap as jest.Mock;
|
|
49
|
+
|
|
50
|
+
const renderWorkspace = (contextOverrides = {}) => {
|
|
51
|
+
const defaultContext = {
|
|
52
|
+
workflowState: 'EDIT_FORM',
|
|
53
|
+
patientUuids: ['patient-a', 'patient-b'],
|
|
54
|
+
activePatientUuid: 'patient-a',
|
|
55
|
+
activeEncounterUuid: null,
|
|
56
|
+
activeVisitUuid: null,
|
|
57
|
+
activeFormUuid: 'group-form',
|
|
58
|
+
activeGroupUuid: 'group-1',
|
|
59
|
+
activeGroupName: 'Nutrition Cohort',
|
|
60
|
+
activeSessionUuid: 'session-1',
|
|
61
|
+
activeSessionMeta: {
|
|
62
|
+
sessionName: 'April Session',
|
|
63
|
+
practitionerName: 'Alice',
|
|
64
|
+
sessionDate: '2026-04-15',
|
|
65
|
+
sessionNotes: 'Bring notebooks',
|
|
66
|
+
},
|
|
67
|
+
groupVisitTypeUuid: 'visit-type-1',
|
|
68
|
+
encounters: {},
|
|
69
|
+
saveEncounter: jest.fn(),
|
|
70
|
+
updateVisitUuid: jest.fn(),
|
|
71
|
+
submitForNext: jest.fn(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return render(
|
|
75
|
+
<GroupFormWorkflowContext.Provider value={{ ...defaultContext, ...contextOverrides } as never}>
|
|
76
|
+
<GroupSessionWorkspace />
|
|
77
|
+
</GroupFormWorkflowContext.Provider>,
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
describe('GroupSessionWorkspace', () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
mockGetGlobalStore.mockReturnValue('ampath-form-state' as never);
|
|
84
|
+
mockUseStore.mockReturnValue({
|
|
85
|
+
'group-form': 'ready',
|
|
86
|
+
} as never);
|
|
87
|
+
mockUseSession.mockReturnValue({
|
|
88
|
+
sessionLocation: {
|
|
89
|
+
uuid: 'session-location',
|
|
90
|
+
display: 'General Hospital',
|
|
91
|
+
},
|
|
92
|
+
} as never);
|
|
93
|
+
mockUseConfig.mockReturnValue({
|
|
94
|
+
groupSessionConcepts: {
|
|
95
|
+
sessionName: 'concept-session-name',
|
|
96
|
+
practitionerName: 'concept-practitioner',
|
|
97
|
+
sessionNotes: 'concept-notes',
|
|
98
|
+
sessionDate: 'concept-date',
|
|
99
|
+
cohortId: 'concept-cohort-id',
|
|
100
|
+
cohortName: 'concept-cohort-name',
|
|
101
|
+
sessionUuid: 'concept-session-uuid',
|
|
102
|
+
},
|
|
103
|
+
} as never);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('builds encounter payloads with group-session metadata when no visit exists yet', () => {
|
|
107
|
+
const updateVisitUuid = jest.fn();
|
|
108
|
+
renderWorkspace({ updateVisitUuid });
|
|
109
|
+
|
|
110
|
+
const [formBootstrapProps] = mockFormBootstrap.mock.calls[0];
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
+
const payload: Record<string, any> = {
|
|
113
|
+
obs: [
|
|
114
|
+
{
|
|
115
|
+
concept: 'weight-concept',
|
|
116
|
+
value: '70',
|
|
117
|
+
groupMembers: [{ concept: 'height-concept', value: '175' }],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
act(() => {
|
|
123
|
+
formBootstrapProps.handleEncounterCreate(payload);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const expectedObsDatetime = new Date('2026-04-15').toISOString();
|
|
127
|
+
|
|
128
|
+
expect(payload.location).toBe('session-location');
|
|
129
|
+
expect(payload.encounterDatetime).toBe(expectedObsDatetime);
|
|
130
|
+
expect(payload.obs[0]).toEqual(
|
|
131
|
+
expect.objectContaining({
|
|
132
|
+
obsDatetime: expectedObsDatetime,
|
|
133
|
+
groupMembers: [
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
concept: 'height-concept',
|
|
136
|
+
value: '175',
|
|
137
|
+
obsDatetime: expectedObsDatetime,
|
|
138
|
+
}),
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
expect(payload.obs).toEqual(
|
|
143
|
+
expect.arrayContaining([
|
|
144
|
+
expect.objectContaining({ concept: 'concept-session-name', value: 'April Session' }),
|
|
145
|
+
expect.objectContaining({ concept: 'concept-practitioner', value: 'Alice' }),
|
|
146
|
+
expect.objectContaining({ concept: 'concept-notes', value: 'Bring notebooks' }),
|
|
147
|
+
expect.objectContaining({ concept: 'concept-date', value: '2026-04-15' }),
|
|
148
|
+
expect.objectContaining({ concept: 'concept-cohort-id', value: 'group-1' }),
|
|
149
|
+
expect.objectContaining({ concept: 'concept-cohort-name', value: 'Nutrition Cohort' }),
|
|
150
|
+
expect.objectContaining({ concept: 'concept-session-uuid', value: 'session-1' }),
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
expect(payload.visit).toEqual({
|
|
154
|
+
startDatetime: '2026-04-15',
|
|
155
|
+
stopDatetime: '2026-04-15',
|
|
156
|
+
uuid: 'generated-visit-uuid',
|
|
157
|
+
patient: {
|
|
158
|
+
uuid: 'patient-a',
|
|
159
|
+
},
|
|
160
|
+
location: {
|
|
161
|
+
uuid: 'session-location',
|
|
162
|
+
},
|
|
163
|
+
visitType: {
|
|
164
|
+
uuid: 'visit-type-1',
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
expect(updateVisitUuid).toHaveBeenCalledWith('generated-visit-uuid');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('wires patient switching and save actions through the workflow callbacks', async () => {
|
|
171
|
+
const user = userEvent.setup();
|
|
172
|
+
const saveEncounter = jest.fn();
|
|
173
|
+
const submitForNext = jest.fn();
|
|
174
|
+
renderWorkspace({ saveEncounter, submitForNext });
|
|
175
|
+
|
|
176
|
+
const [formBootstrapProps] = mockFormBootstrap.mock.calls[0];
|
|
177
|
+
|
|
178
|
+
act(() => {
|
|
179
|
+
formBootstrapProps.handlePostResponse({ uuid: 'encounter-1' });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(saveEncounter).toHaveBeenCalledWith('encounter-1');
|
|
183
|
+
|
|
184
|
+
await user.click(screen.getByTestId('patient-card-patient-b'));
|
|
185
|
+
expect(submitForNext).toHaveBeenCalledWith('patient-b');
|
|
186
|
+
|
|
187
|
+
await user.click(screen.getByRole('button', { name: 'Next patient' }));
|
|
188
|
+
expect(submitForNext).toHaveBeenCalledWith();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { useFormContext } from 'react-hook-form';
|
|
5
|
+
import GroupFormWorkflowContext from '../context/GroupFormWorkflowContext';
|
|
6
|
+
import SessionMetaWorkspace from './SessionMetaWorkspace';
|
|
7
|
+
|
|
8
|
+
jest.mock('../CancelModal', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: () => null,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('./SessionDetailsForm', () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: function MockSessionDetailsForm() {
|
|
16
|
+
const { register, setValue } = useFormContext();
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setValue('sessionName', 'April Session');
|
|
20
|
+
setValue('practitionerName', 'Alice');
|
|
21
|
+
setValue('sessionDate', ['2026-04-15']);
|
|
22
|
+
setValue('sessionNotes', 'Bring notebooks');
|
|
23
|
+
}, [setValue]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<input aria-label="Session Name" {...register('sessionName', { required: true })} />
|
|
28
|
+
<input aria-label="Practitioner Name" {...register('practitionerName', { required: true })} />
|
|
29
|
+
<input aria-label="Session Notes" {...register('sessionNotes', { required: true })} />
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const renderSessionMetaWorkspace = (contextOverrides = {}) =>
|
|
36
|
+
render(
|
|
37
|
+
<GroupFormWorkflowContext.Provider
|
|
38
|
+
value={
|
|
39
|
+
{
|
|
40
|
+
workflowState: 'NEW_GROUP_SESSION',
|
|
41
|
+
patientUuids: ['patient-a'],
|
|
42
|
+
activeGroupUuid: 'group-1',
|
|
43
|
+
setSessionMeta: jest.fn(),
|
|
44
|
+
...contextOverrides,
|
|
45
|
+
} as never
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
<SessionMetaWorkspace />
|
|
49
|
+
</GroupFormWorkflowContext.Provider>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
describe('SessionMetaWorkspace', () => {
|
|
53
|
+
it('submits the session metadata with the normalized session date', async () => {
|
|
54
|
+
const user = userEvent.setup();
|
|
55
|
+
const setSessionMeta = jest.fn();
|
|
56
|
+
renderSessionMetaWorkspace({ setSessionMeta });
|
|
57
|
+
|
|
58
|
+
await user.click(screen.getByRole('button', { name: 'Create New Session' }));
|
|
59
|
+
|
|
60
|
+
await waitFor(() => {
|
|
61
|
+
expect(setSessionMeta).toHaveBeenCalledWith(
|
|
62
|
+
expect.objectContaining({
|
|
63
|
+
groupUuid: 'group-1',
|
|
64
|
+
sessionName: 'April Session',
|
|
65
|
+
practitionerName: 'Alice',
|
|
66
|
+
sessionDate: '2026-04-15',
|
|
67
|
+
sessionNotes: 'Bring notebooks',
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('shows the group selection error when submitted without a chosen group', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
const setSessionMeta = jest.fn();
|
|
76
|
+
renderSessionMetaWorkspace({
|
|
77
|
+
activeGroupUuid: null,
|
|
78
|
+
setSessionMeta,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await user.click(screen.getByRole('button', { name: 'Create New Session' }));
|
|
82
|
+
|
|
83
|
+
expect(await screen.findByText('Please choose a group.')).toBeInTheDocument();
|
|
84
|
+
expect(setSessionMeta).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('disables session creation until the participant list is populated', () => {
|
|
88
|
+
renderSessionMetaWorkspace({
|
|
89
|
+
patientUuids: [],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(screen.getByRole('button', { name: 'Create New Session' })).toBeDisabled();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { showSnackbar, useConfig, useSession } from '@openmrs/esm-framework';
|
|
5
|
+
import GroupFormWorkflowContext from '../../context/GroupFormWorkflowContext';
|
|
6
|
+
import GroupSearchHeader from './GroupSearchHeader';
|
|
7
|
+
|
|
8
|
+
let selectedGroup;
|
|
9
|
+
|
|
10
|
+
jest.mock('../group-search/CompactGroupSearch', () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
default: ({ selectGroupAction }) => (
|
|
13
|
+
<button data-testid="compact-group-search" onClick={() => selectGroupAction(selectedGroup)}>
|
|
14
|
+
Select group
|
|
15
|
+
</button>
|
|
16
|
+
),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock('../../add-group-modal/AddGroupModal', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: ({ isOpen }) => (isOpen ? <div data-testid="add-group-modal" /> : null),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
25
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
26
|
+
const mockUseSession = jest.mocked(useSession);
|
|
27
|
+
|
|
28
|
+
const renderGroupSearchHeader = (contextOverrides = {}) =>
|
|
29
|
+
render(
|
|
30
|
+
<GroupFormWorkflowContext.Provider
|
|
31
|
+
value={
|
|
32
|
+
{
|
|
33
|
+
activeGroupUuid: null,
|
|
34
|
+
setGroup: jest.fn(),
|
|
35
|
+
destroySession: jest.fn(),
|
|
36
|
+
...contextOverrides,
|
|
37
|
+
} as never
|
|
38
|
+
}
|
|
39
|
+
>
|
|
40
|
+
<GroupSearchHeader />
|
|
41
|
+
</GroupFormWorkflowContext.Provider>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
describe('GroupSearchHeader', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockUseSession.mockReturnValue({
|
|
47
|
+
sessionLocation: {
|
|
48
|
+
uuid: 'session-location',
|
|
49
|
+
display: 'General Hospital',
|
|
50
|
+
},
|
|
51
|
+
} as never);
|
|
52
|
+
mockUseConfig.mockReturnValue({
|
|
53
|
+
enforcePatientListLocationMatch: true,
|
|
54
|
+
} as never);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('blocks group selection when location enforcement is enabled and locations mismatch', async () => {
|
|
58
|
+
const user = userEvent.setup();
|
|
59
|
+
const setGroup = jest.fn();
|
|
60
|
+
selectedGroup = {
|
|
61
|
+
uuid: 'group-1',
|
|
62
|
+
location: {
|
|
63
|
+
uuid: 'other-location',
|
|
64
|
+
display: 'Remote Clinic',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
renderGroupSearchHeader({ setGroup });
|
|
69
|
+
|
|
70
|
+
await user.click(screen.getByTestId('compact-group-search'));
|
|
71
|
+
|
|
72
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
kind: 'error',
|
|
75
|
+
title: 'Location Mismatch',
|
|
76
|
+
subtitle: 'Cannot select group from Remote Clinic for a session at General Hospital',
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
expect(setGroup).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('sorts cohort members by display name before storing the selected group', async () => {
|
|
83
|
+
const user = userEvent.setup();
|
|
84
|
+
const setGroup = jest.fn();
|
|
85
|
+
selectedGroup = {
|
|
86
|
+
uuid: 'group-1',
|
|
87
|
+
location: {
|
|
88
|
+
uuid: 'session-location',
|
|
89
|
+
display: 'General Hospital',
|
|
90
|
+
},
|
|
91
|
+
cohortMembers: [
|
|
92
|
+
{ patient: { person: { names: [{ display: 'zoe zebra' }] } } },
|
|
93
|
+
{ patient: { person: { names: [{ display: 'Alice Able' }] } } },
|
|
94
|
+
{ patient: { person: { names: [{ display: 'ben Brown' }] } } },
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
renderGroupSearchHeader({ setGroup });
|
|
99
|
+
|
|
100
|
+
await user.click(screen.getByTestId('compact-group-search'));
|
|
101
|
+
|
|
102
|
+
expect(setGroup).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(setGroup.mock.calls[0][0].cohortMembers.map((member) => member.patient.person.names[0].display)).toEqual([
|
|
104
|
+
'Alice Able',
|
|
105
|
+
'ben Brown',
|
|
106
|
+
'zoe zebra',
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('opens the add-group modal and lets the user cancel the session', async () => {
|
|
111
|
+
const user = userEvent.setup();
|
|
112
|
+
const destroySession = jest.fn();
|
|
113
|
+
renderGroupSearchHeader({ destroySession });
|
|
114
|
+
|
|
115
|
+
await user.click(screen.getByRole('button', { name: 'Create New Group' }));
|
|
116
|
+
expect(screen.getByTestId('add-group-modal')).toBeInTheDocument();
|
|
117
|
+
|
|
118
|
+
await user.click(screen.getByRole('button', { name: 'Cancel' }));
|
|
119
|
+
expect(destroySession).toHaveBeenCalledTimes(1);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
import { fetchCurrentPatient } from '@openmrs/esm-framework';
|
|
4
|
+
import useGetPatient from './useGetPatient';
|
|
5
|
+
|
|
6
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
7
|
+
fetchCurrentPatient: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
type Deferred<T> = {
|
|
11
|
+
promise: Promise<T>;
|
|
12
|
+
resolve: (value: T) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createDeferred = <T,>(): Deferred<T> => {
|
|
16
|
+
let resolve: (value: T) => void;
|
|
17
|
+
const promise = new Promise<T>((res) => {
|
|
18
|
+
resolve = res;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
promise,
|
|
23
|
+
resolve,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const mockFetchCurrentPatient = fetchCurrentPatient as jest.MockedFunction<typeof fetchCurrentPatient>;
|
|
28
|
+
|
|
29
|
+
const TestHarness = ({ patientUuid }: { patientUuid?: string }) => {
|
|
30
|
+
const patient = useGetPatient(patientUuid);
|
|
31
|
+
const patientName = patient ? (patient as { name: string }).name : 'none';
|
|
32
|
+
|
|
33
|
+
return <div data-testid="patient-name">{patientName}</div>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('useGetPatient', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jest.clearAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('ignores stale responses after patientUuid changes', async () => {
|
|
42
|
+
const patientARequest = createDeferred<fhir.Patient>();
|
|
43
|
+
const patientBRequest = createDeferred<fhir.Patient>();
|
|
44
|
+
|
|
45
|
+
mockFetchCurrentPatient.mockImplementation((uuid) => {
|
|
46
|
+
if (uuid === 'patient-a') {
|
|
47
|
+
return patientARequest.promise;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (uuid === 'patient-b') {
|
|
51
|
+
return patientBRequest.promise;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error(`Unexpected patient uuid: ${uuid}`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const { rerender } = render(<TestHarness patientUuid="patient-a" />);
|
|
58
|
+
|
|
59
|
+
rerender(<TestHarness patientUuid="patient-b" />);
|
|
60
|
+
|
|
61
|
+
await act(async () => {
|
|
62
|
+
patientBRequest.resolve({ id: 'patient-b', name: 'Patient B' } as unknown as fhir.Patient);
|
|
63
|
+
await patientBRequest.promise;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(screen.getByTestId('patient-name')).toHaveTextContent('Patient B');
|
|
67
|
+
|
|
68
|
+
await act(async () => {
|
|
69
|
+
patientARequest.resolve({ id: 'patient-a', name: 'Patient A' } as unknown as fhir.Patient);
|
|
70
|
+
await patientARequest.promise;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(screen.getByTestId('patient-name')).toHaveTextContent('Patient B');
|
|
74
|
+
expect(mockFetchCurrentPatient).toHaveBeenNthCalledWith(1, 'patient-a');
|
|
75
|
+
expect(mockFetchCurrentPatient).toHaveBeenNthCalledWith(2, 'patient-b');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('clears the previous patient while the next patient is loading', async () => {
|
|
79
|
+
const patientBRequest = createDeferred<fhir.Patient>();
|
|
80
|
+
mockFetchCurrentPatient.mockImplementation((uuid) => {
|
|
81
|
+
if (uuid === 'patient-a') {
|
|
82
|
+
return Promise.resolve({ id: 'patient-a', name: 'Patient A' } as unknown as fhir.Patient);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (uuid === 'patient-b') {
|
|
86
|
+
return patientBRequest.promise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(`Unexpected patient uuid: ${uuid}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const { rerender } = render(<TestHarness patientUuid="patient-a" />);
|
|
93
|
+
|
|
94
|
+
expect(await screen.findByText('Patient A')).toBeInTheDocument();
|
|
95
|
+
|
|
96
|
+
rerender(<TestHarness patientUuid="patient-b" />);
|
|
97
|
+
|
|
98
|
+
expect(screen.getByTestId('patient-name')).toHaveTextContent('none');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('clears the patient and skips fetching when patientUuid is removed', async () => {
|
|
102
|
+
mockFetchCurrentPatient.mockResolvedValue({ id: 'patient-a', name: 'Patient A' } as unknown as fhir.Patient);
|
|
103
|
+
|
|
104
|
+
const { rerender } = render(<TestHarness patientUuid="patient-a" />);
|
|
105
|
+
|
|
106
|
+
expect(await screen.findByText('Patient A')).toBeInTheDocument();
|
|
107
|
+
|
|
108
|
+
rerender(<TestHarness />);
|
|
109
|
+
|
|
110
|
+
expect(screen.getByTestId('patient-name')).toHaveTextContent('none');
|
|
111
|
+
expect(mockFetchCurrentPatient).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
});
|