@openmrs/esm-fast-data-entry-app 1.4.2-pre.702 → 1.4.2-pre.706
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/9814.js +1 -1
- package/dist/9814.js.map +1 -1
- package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +6 -6
- 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 +1 -1
- package/src/FormBootstrap.test.tsx +112 -0
- package/src/FormBootstrap.tsx +4 -1
- 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/src/hooks/useGetPatient.ts +13 -4
- package/e2e/specs/sample-test.spec.ts +0 -10
|
@@ -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
|
+
});
|
|
@@ -5,11 +5,20 @@ const useGetPatient = (patientUuid) => {
|
|
|
5
5
|
const [patient, setPatient] = useState(null);
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let cancelled = false;
|
|
9
|
+
setPatient(null);
|
|
10
|
+
|
|
11
|
+
if (patientUuid) {
|
|
12
|
+
fetchCurrentPatient(patientUuid).then((result) => {
|
|
13
|
+
if (!cancelled) {
|
|
14
|
+
setPatient(result);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
12
17
|
}
|
|
18
|
+
|
|
19
|
+
return () => {
|
|
20
|
+
cancelled = true;
|
|
21
|
+
};
|
|
13
22
|
}, [patientUuid]);
|
|
14
23
|
|
|
15
24
|
const getPatient = async (uuid) => {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
import { FastDataEntryPage } from '../pages';
|
|
3
|
-
|
|
4
|
-
// This test is a sample E2E test. You can delete it.
|
|
5
|
-
|
|
6
|
-
test('sample-test', async ({ page }) => {
|
|
7
|
-
const fastDataEntryPage = new FastDataEntryPage(page);
|
|
8
|
-
await fastDataEntryPage.goto();
|
|
9
|
-
await expect(page.getByText('Fast Data Entry')).toBeVisible();
|
|
10
|
-
});
|