@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-fast-data-entry-app",
|
|
3
|
-
"version": "1.4.2-pre.
|
|
3
|
+
"version": "1.4.2-pre.707",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "O3 frontend module for fast data entry using the OpenMRS Angular Form Engine",
|
|
6
6
|
"browser": "dist/openmrs-esm-fast-data-entry-app.js",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"semver": "^7.5.4",
|
|
90
90
|
"swr": "^2.2.4",
|
|
91
91
|
"turbo": "^2.5.2",
|
|
92
|
-
"typescript": "^
|
|
92
|
+
"typescript": "^5.0.0"
|
|
93
93
|
},
|
|
94
94
|
"dependencies": {
|
|
95
95
|
"dotenv": "^16.4.7",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
import { detach, ExtensionSlot } from '@openmrs/esm-framework';
|
|
4
|
+
import GroupFormWorkflowContext from './context/GroupFormWorkflowContext';
|
|
5
|
+
import useGetPatient from './hooks/useGetPatient';
|
|
6
|
+
import FormBootstrap from './FormBootstrap';
|
|
7
|
+
|
|
8
|
+
jest.mock('./hooks/useGetPatient', () => jest.fn());
|
|
9
|
+
|
|
10
|
+
const mockDetach = jest.mocked(detach);
|
|
11
|
+
const mockExtensionSlot = jest.mocked(ExtensionSlot);
|
|
12
|
+
const mockUseGetPatient = useGetPatient as jest.MockedFunction<typeof useGetPatient>;
|
|
13
|
+
|
|
14
|
+
const renderFormBootstrap = (props = {}) =>
|
|
15
|
+
render(
|
|
16
|
+
<GroupFormWorkflowContext.Provider
|
|
17
|
+
value={
|
|
18
|
+
{
|
|
19
|
+
activeSessionMeta: {
|
|
20
|
+
sessionName: 'April Session',
|
|
21
|
+
practitionerName: 'Alice',
|
|
22
|
+
sessionDate: '2026-04-15',
|
|
23
|
+
sessionNotes: 'Follow-up notes',
|
|
24
|
+
},
|
|
25
|
+
} as never
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
<FormBootstrap
|
|
29
|
+
formUuid="triage-form"
|
|
30
|
+
patientUuid="patient-1"
|
|
31
|
+
visitUuid="visit-1"
|
|
32
|
+
visitTypeUuid="visit-type-1"
|
|
33
|
+
encounterUuid="encounter-1"
|
|
34
|
+
handlePostResponse={jest.fn()}
|
|
35
|
+
handleEncounterCreate={jest.fn()}
|
|
36
|
+
handleOnValidate={jest.fn()}
|
|
37
|
+
hidePatientBanner={true}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
</GroupFormWorkflowContext.Provider>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
describe('FormBootstrap', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockExtensionSlot.mockImplementation(() => <div data-testid="form-widget-slot" />);
|
|
46
|
+
mockUseGetPatient.mockReturnValue({
|
|
47
|
+
id: 'patient-1',
|
|
48
|
+
name: [{ given: ['Ada'], family: 'Lovelace' }],
|
|
49
|
+
} as fhir.Patient);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
jest.useRealTimers();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('passes the expected widget state once the patient is available', () => {
|
|
57
|
+
renderFormBootstrap();
|
|
58
|
+
|
|
59
|
+
expect(screen.getByTestId('form-widget-slot')).toBeInTheDocument();
|
|
60
|
+
|
|
61
|
+
const [{ name, state }] = mockExtensionSlot.mock.calls[0];
|
|
62
|
+
expect(name).toBe('form-widget-slot');
|
|
63
|
+
expect(state).toEqual(
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
view: 'form',
|
|
66
|
+
formUuid: 'triage-form',
|
|
67
|
+
visitUuid: 'visit-1',
|
|
68
|
+
visitTypeUuid: 'visit-type-1',
|
|
69
|
+
patientUuid: 'patient-1',
|
|
70
|
+
patient: expect.objectContaining({ id: 'patient-1' }),
|
|
71
|
+
encounterUuid: 'encounter-1',
|
|
72
|
+
showDiscardSubmitButtons: false,
|
|
73
|
+
hideControls: true,
|
|
74
|
+
hidePatientBanner: true,
|
|
75
|
+
preFilledQuestions: {
|
|
76
|
+
sessionName: 'April Session',
|
|
77
|
+
practitionerName: 'Alice',
|
|
78
|
+
sessionDate: '2026-04-15',
|
|
79
|
+
sessionNotes: 'Follow-up notes',
|
|
80
|
+
encDate: '2026-04-15',
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('refreshes the widget after submit and detaches it on unmount', () => {
|
|
87
|
+
jest.useFakeTimers();
|
|
88
|
+
const handlePostResponse = jest.fn();
|
|
89
|
+
const { unmount } = renderFormBootstrap({ handlePostResponse });
|
|
90
|
+
|
|
91
|
+
const [{ state }] = mockExtensionSlot.mock.calls[0];
|
|
92
|
+
const handlePostResponseFromState = state.handlePostResponse as (encounter: { uuid: string }) => void;
|
|
93
|
+
|
|
94
|
+
act(() => {
|
|
95
|
+
handlePostResponseFromState({ uuid: 'encounter-2' });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(handlePostResponse).toHaveBeenCalledWith({ uuid: 'encounter-2' });
|
|
99
|
+
expect(screen.queryByTestId('form-widget-slot')).not.toBeInTheDocument();
|
|
100
|
+
|
|
101
|
+
act(() => {
|
|
102
|
+
jest.advanceTimersByTime(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(screen.getByTestId('form-widget-slot')).toBeInTheDocument();
|
|
106
|
+
expect(mockExtensionSlot).toHaveBeenCalledTimes(2);
|
|
107
|
+
|
|
108
|
+
unmount();
|
|
109
|
+
|
|
110
|
+
expect(mockDetach).toHaveBeenCalledWith('form-widget-slot', 'form-widget-slot');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { navigate } from '@openmrs/esm-framework';
|
|
2
|
+
import { initialWorkflowState } from './FormWorkflowContext';
|
|
3
|
+
import reducer, { fdeWorkflowStorageName, fdeWorkflowStorageVersion } from './FormWorkflowReducer';
|
|
4
|
+
|
|
5
|
+
const mockNavigate = jest.mocked(navigate);
|
|
6
|
+
|
|
7
|
+
const buildState = (formStateOverrides = {}) => ({
|
|
8
|
+
...initialWorkflowState,
|
|
9
|
+
activeFormUuid: 'triage-form',
|
|
10
|
+
userUuid: 'user-1',
|
|
11
|
+
forms: {
|
|
12
|
+
'triage-form': {
|
|
13
|
+
workflowState: 'NEW_PATIENT',
|
|
14
|
+
activePatientUuid: null,
|
|
15
|
+
activeEncounterUuid: null,
|
|
16
|
+
patientUuids: [],
|
|
17
|
+
encounters: {},
|
|
18
|
+
...formStateOverrides,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('FormWorkflowReducer', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
localStorage.clear();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('initializes a fresh workflow state when there is no saved session', () => {
|
|
29
|
+
const state = reducer(initialWorkflowState, {
|
|
30
|
+
type: 'INITIALIZE_WORKFLOW_STATE',
|
|
31
|
+
activeFormUuid: 'triage-form',
|
|
32
|
+
userUuid: 'user-1',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(state.activeFormUuid).toBe('triage-form');
|
|
36
|
+
expect(state.forms['triage-form']).toMatchObject({
|
|
37
|
+
workflowState: 'NEW_PATIENT',
|
|
38
|
+
activePatientUuid: null,
|
|
39
|
+
activeEncounterUuid: null,
|
|
40
|
+
patientUuids: [],
|
|
41
|
+
encounters: {},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(JSON.parse(localStorage.getItem(`${fdeWorkflowStorageName}:user-1`))).toMatchObject({
|
|
45
|
+
_storageVersion: fdeWorkflowStorageVersion,
|
|
46
|
+
activeFormUuid: 'triage-form',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('restores a saved workflow and applies a patient from the URL when provided', () => {
|
|
51
|
+
localStorage.setItem(
|
|
52
|
+
`${fdeWorkflowStorageName}:user-1`,
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
_storageVersion: fdeWorkflowStorageVersion,
|
|
55
|
+
activeFormUuid: 'triage-form',
|
|
56
|
+
userUuid: 'user-1',
|
|
57
|
+
forms: {
|
|
58
|
+
'triage-form': {
|
|
59
|
+
workflowState: 'REVIEW',
|
|
60
|
+
activePatientUuid: null,
|
|
61
|
+
activeEncounterUuid: null,
|
|
62
|
+
patientUuids: ['patient-a'],
|
|
63
|
+
encounters: {},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const state = reducer(initialWorkflowState, {
|
|
70
|
+
type: 'INITIALIZE_WORKFLOW_STATE',
|
|
71
|
+
activeFormUuid: 'triage-form',
|
|
72
|
+
newPatientUuid: 'patient-b',
|
|
73
|
+
userUuid: 'user-1',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(state.forms['triage-form']).toMatchObject({
|
|
77
|
+
workflowState: 'EDIT_FORM',
|
|
78
|
+
activePatientUuid: 'patient-b',
|
|
79
|
+
});
|
|
80
|
+
expect(state.forms['triage-form'].patientUuids).toEqual(['patient-a', 'patient-b']);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('dispatches the submit event and moves the workflow into SUBMIT_FOR_NEXT', () => {
|
|
84
|
+
const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent');
|
|
85
|
+
const state = buildState({
|
|
86
|
+
workflowState: 'EDIT_FORM',
|
|
87
|
+
activePatientUuid: 'patient-a',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const nextState = reducer(state, {
|
|
91
|
+
type: 'SUBMIT_FOR_NEXT',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(nextState.forms['triage-form'].workflowState).toBe('SUBMIT_FOR_NEXT');
|
|
95
|
+
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
|
96
|
+
|
|
97
|
+
const event = dispatchEventSpy.mock.calls[0][0] as CustomEvent;
|
|
98
|
+
expect(event.type).toBe('ampath-form-action');
|
|
99
|
+
expect(event.detail).toEqual({
|
|
100
|
+
formUuid: 'triage-form',
|
|
101
|
+
patientUuid: 'patient-a',
|
|
102
|
+
action: 'onSubmit',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('stores the encounter and returns to NEW_PATIENT after saving for next', () => {
|
|
107
|
+
const state = buildState({
|
|
108
|
+
workflowState: 'SUBMIT_FOR_NEXT',
|
|
109
|
+
activePatientUuid: 'patient-a',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const nextState = reducer(state, {
|
|
113
|
+
type: 'SAVE_ENCOUNTER',
|
|
114
|
+
encounterUuid: 'encounter-1',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(nextState.forms['triage-form']).toMatchObject({
|
|
118
|
+
workflowState: 'NEW_PATIENT',
|
|
119
|
+
activePatientUuid: null,
|
|
120
|
+
activeEncounterUuid: null,
|
|
121
|
+
encounters: {
|
|
122
|
+
'patient-a': 'encounter-1',
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('destroys the active form session and navigates back to the forms page after complete', () => {
|
|
128
|
+
const state = buildState({
|
|
129
|
+
workflowState: 'SUBMIT_FOR_COMPLETE',
|
|
130
|
+
activePatientUuid: 'patient-a',
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const nextState = reducer(state, {
|
|
134
|
+
type: 'SAVE_ENCOUNTER',
|
|
135
|
+
encounterUuid: 'encounter-1',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(nextState.activeFormUuid).toBeNull();
|
|
139
|
+
expect(nextState.forms).toEqual({});
|
|
140
|
+
expect(mockNavigate).toHaveBeenCalledWith({ to: '${openmrsSpaBase}/forms' });
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { navigate } from '@openmrs/esm-framework';
|
|
2
|
+
import { initialWorkflowState } from './GroupFormWorkflowContext';
|
|
3
|
+
import reducer, { fdeGroupWorkflowStorageName, fdeGroupWorkflowStorageVersion } from './GroupFormWorkflowReducer';
|
|
4
|
+
|
|
5
|
+
jest.mock('uuid', () => ({
|
|
6
|
+
v4: jest.fn(() => 'generated-session-uuid'),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const mockNavigate = jest.mocked(navigate);
|
|
10
|
+
|
|
11
|
+
const buildState = (formStateOverrides = {}, rootStateOverrides = {}) => ({
|
|
12
|
+
...initialWorkflowState,
|
|
13
|
+
activeFormUuid: 'group-form',
|
|
14
|
+
userUuid: 'user-1',
|
|
15
|
+
forms: {
|
|
16
|
+
'group-form': {
|
|
17
|
+
workflowState: 'NEW_GROUP_SESSION',
|
|
18
|
+
groupUuid: null,
|
|
19
|
+
groupName: null,
|
|
20
|
+
groupMembers: [],
|
|
21
|
+
activePatientUuid: null,
|
|
22
|
+
activeEncounterUuid: null,
|
|
23
|
+
activeVisitUuid: null,
|
|
24
|
+
activeSessionUuid: null,
|
|
25
|
+
patientUuids: [],
|
|
26
|
+
encounters: {},
|
|
27
|
+
visits: {},
|
|
28
|
+
...formStateOverrides,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
...rootStateOverrides,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('GroupFormWorkflowReducer', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
localStorage.clear();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('initializes a fresh group workflow state when there is no saved session', () => {
|
|
40
|
+
const state = reducer(initialWorkflowState, {
|
|
41
|
+
type: 'INITIALIZE_WORKFLOW_STATE',
|
|
42
|
+
activeFormUuid: 'group-form',
|
|
43
|
+
userUuid: 'user-1',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(state.activeFormUuid).toBe('group-form');
|
|
47
|
+
expect(state.forms['group-form']).toMatchObject({
|
|
48
|
+
workflowState: 'NEW_GROUP_SESSION',
|
|
49
|
+
groupUuid: null,
|
|
50
|
+
patientUuids: [],
|
|
51
|
+
encounters: {},
|
|
52
|
+
visits: {},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(JSON.parse(localStorage.getItem(`${fdeGroupWorkflowStorageName}:user-1`))).toMatchObject({
|
|
56
|
+
_storageVersion: fdeGroupWorkflowStorageVersion,
|
|
57
|
+
activeFormUuid: 'group-form',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('restores a saved workflow and derives the current patient and visit state', () => {
|
|
62
|
+
localStorage.setItem(
|
|
63
|
+
`${fdeGroupWorkflowStorageName}:user-1`,
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
_storageVersion: fdeGroupWorkflowStorageVersion,
|
|
66
|
+
activeFormUuid: 'group-form',
|
|
67
|
+
userUuid: 'user-1',
|
|
68
|
+
forms: {
|
|
69
|
+
'group-form': {
|
|
70
|
+
workflowState: 'EDIT_FORM',
|
|
71
|
+
patientUuids: ['patient-a', 'patient-b'],
|
|
72
|
+
encounters: {
|
|
73
|
+
'patient-a': 'encounter-a',
|
|
74
|
+
},
|
|
75
|
+
visits: {
|
|
76
|
+
'patient-a': 'visit-a',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const state = reducer(initialWorkflowState, {
|
|
84
|
+
type: 'INITIALIZE_WORKFLOW_STATE',
|
|
85
|
+
activeFormUuid: 'group-form',
|
|
86
|
+
userUuid: 'user-1',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(state.forms['group-form']).toMatchObject({
|
|
90
|
+
workflowState: 'EDIT_FORM',
|
|
91
|
+
activePatientUuid: 'patient-a',
|
|
92
|
+
activeEncounterUuid: 'encounter-a',
|
|
93
|
+
activeVisitUuid: 'visit-a',
|
|
94
|
+
activeSessionUuid: 'generated-session-uuid',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('stores the selected group and clears the active patient state', () => {
|
|
99
|
+
const state = buildState({
|
|
100
|
+
activePatientUuid: 'patient-z',
|
|
101
|
+
activeEncounterUuid: 'encounter-z',
|
|
102
|
+
activeVisitUuid: 'visit-z',
|
|
103
|
+
activeSessionUuid: 'session-z',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const nextState = reducer(state, {
|
|
107
|
+
type: 'SET_GROUP',
|
|
108
|
+
group: {
|
|
109
|
+
uuid: 'cohort-1',
|
|
110
|
+
name: 'Nutrition Cohort',
|
|
111
|
+
cohortMembers: [{ patient: { uuid: 'patient-a' } }, { patient: { uuid: 'patient-b' } }],
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(nextState.forms['group-form']).toMatchObject({
|
|
116
|
+
groupUuid: 'cohort-1',
|
|
117
|
+
groupName: 'Nutrition Cohort',
|
|
118
|
+
groupMembers: ['patient-a', 'patient-b'],
|
|
119
|
+
patientUuids: ['patient-a', 'patient-b'],
|
|
120
|
+
activePatientUuid: null,
|
|
121
|
+
activeEncounterUuid: null,
|
|
122
|
+
activeVisitUuid: null,
|
|
123
|
+
activeSessionUuid: null,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('sets session metadata and moves to the first patient in edit mode', () => {
|
|
128
|
+
const state = buildState({
|
|
129
|
+
patientUuids: ['patient-a', 'patient-b'],
|
|
130
|
+
encounters: {
|
|
131
|
+
'patient-a': 'encounter-a',
|
|
132
|
+
},
|
|
133
|
+
visits: {
|
|
134
|
+
'patient-a': 'visit-a',
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const nextState = reducer(state, {
|
|
139
|
+
type: 'SET_SESSION_META',
|
|
140
|
+
meta: {
|
|
141
|
+
sessionName: 'April Session',
|
|
142
|
+
practitionerName: 'Alice',
|
|
143
|
+
sessionDate: '2026-04-15',
|
|
144
|
+
sessionNotes: 'Notes',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(nextState.forms['group-form']).toMatchObject({
|
|
149
|
+
sessionMeta: {
|
|
150
|
+
sessionName: 'April Session',
|
|
151
|
+
practitionerName: 'Alice',
|
|
152
|
+
sessionDate: '2026-04-15',
|
|
153
|
+
sessionNotes: 'Notes',
|
|
154
|
+
},
|
|
155
|
+
activePatientUuid: 'patient-a',
|
|
156
|
+
activeEncounterUuid: 'encounter-a',
|
|
157
|
+
activeVisitUuid: 'visit-a',
|
|
158
|
+
activeSessionUuid: 'generated-session-uuid',
|
|
159
|
+
workflowState: 'EDIT_FORM',
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('adds and removes patient UUIDs without duplicating entries', () => {
|
|
164
|
+
const state = buildState({
|
|
165
|
+
patientUuids: ['patient-a'],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const unchangedState = reducer(state, {
|
|
169
|
+
type: 'ADD_PATIENT_UUID',
|
|
170
|
+
patientUuid: 'patient-a',
|
|
171
|
+
});
|
|
172
|
+
expect(unchangedState).toBe(state);
|
|
173
|
+
|
|
174
|
+
const addedState = reducer(state, {
|
|
175
|
+
type: 'ADD_PATIENT_UUID',
|
|
176
|
+
patientUuid: 'patient-b',
|
|
177
|
+
});
|
|
178
|
+
expect(addedState.forms['group-form'].patientUuids).toEqual(['patient-a', 'patient-b']);
|
|
179
|
+
|
|
180
|
+
const removedState = reducer(addedState, {
|
|
181
|
+
type: 'REMOVE_PATIENT_UUID',
|
|
182
|
+
patientUuid: 'patient-a',
|
|
183
|
+
});
|
|
184
|
+
expect(removedState.forms['group-form'].patientUuids).toEqual(['patient-b']);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('dispatches validate events and stores the visit UUID for the active patient', () => {
|
|
188
|
+
const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent');
|
|
189
|
+
const state = buildState({
|
|
190
|
+
workflowState: 'EDIT_FORM',
|
|
191
|
+
activePatientUuid: 'patient-a',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const validatingState = reducer(state, {
|
|
195
|
+
type: 'VALIDATE_FOR_NEXT',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(validatingState.forms['group-form'].workflowState).toBe('VALIDATE_FOR_NEXT');
|
|
199
|
+
const event = dispatchEventSpy.mock.calls[0][0] as CustomEvent;
|
|
200
|
+
expect(event.detail).toEqual({
|
|
201
|
+
formUuid: 'group-form',
|
|
202
|
+
patientUuid: 'patient-a',
|
|
203
|
+
action: 'validateForm',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const visitState = reducer(state, {
|
|
207
|
+
type: 'UPDATE_VISIT_UUID',
|
|
208
|
+
visitUuid: 'visit-a',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(visitState.forms['group-form']).toMatchObject({
|
|
212
|
+
activeVisitUuid: 'visit-a',
|
|
213
|
+
visits: {
|
|
214
|
+
'patient-a': 'visit-a',
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('submits for next and advances to the requested patient after saving', () => {
|
|
220
|
+
const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent');
|
|
221
|
+
const state = buildState(
|
|
222
|
+
{
|
|
223
|
+
workflowState: 'EDIT_FORM',
|
|
224
|
+
activePatientUuid: 'patient-a',
|
|
225
|
+
patientUuids: ['patient-a', 'patient-b', 'patient-c'],
|
|
226
|
+
visits: {
|
|
227
|
+
'patient-c': 'visit-c',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
nextPatientUuid: null,
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const submittingState = reducer(state, {
|
|
236
|
+
type: 'SUBMIT_FOR_NEXT',
|
|
237
|
+
nextPatientUuid: 'patient-c',
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(submittingState.forms['group-form'].workflowState).toBe('SUBMIT_FOR_NEXT');
|
|
241
|
+
expect(submittingState.nextPatientUuid).toBe('patient-c');
|
|
242
|
+
const submitEvent = dispatchEventSpy.mock.calls[0][0] as CustomEvent;
|
|
243
|
+
expect(submitEvent.detail).toEqual({
|
|
244
|
+
formUuid: 'group-form',
|
|
245
|
+
patientUuid: 'patient-a',
|
|
246
|
+
action: 'onSubmit',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const savedState = reducer(submittingState, {
|
|
250
|
+
type: 'SAVE_ENCOUNTER',
|
|
251
|
+
encounterUuid: 'encounter-a',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(savedState.forms['group-form']).toMatchObject({
|
|
255
|
+
workflowState: 'EDIT_FORM',
|
|
256
|
+
activePatientUuid: 'patient-c',
|
|
257
|
+
activeEncounterUuid: null,
|
|
258
|
+
activeVisitUuid: 'visit-c',
|
|
259
|
+
encounters: {
|
|
260
|
+
'patient-a': 'encounter-a',
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('clears the active session pointers when going to review', () => {
|
|
266
|
+
const state = buildState({
|
|
267
|
+
workflowState: 'EDIT_FORM',
|
|
268
|
+
activePatientUuid: 'patient-a',
|
|
269
|
+
activeEncounterUuid: 'encounter-a',
|
|
270
|
+
activeVisitUuid: 'visit-a',
|
|
271
|
+
activeSessionUuid: 'session-a',
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const nextState = reducer(state, {
|
|
275
|
+
type: 'GO_TO_REVIEW',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(nextState.forms['group-form']).toMatchObject({
|
|
279
|
+
workflowState: 'REVIEW',
|
|
280
|
+
activePatientUuid: null,
|
|
281
|
+
activeEncounterUuid: null,
|
|
282
|
+
activeVisitUuid: null,
|
|
283
|
+
activeSessionUuid: null,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('destroys the active group session and navigates back to forms', () => {
|
|
288
|
+
const state = buildState({
|
|
289
|
+
workflowState: 'SUBMIT_FOR_COMPLETE',
|
|
290
|
+
activePatientUuid: 'patient-a',
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const nextState = reducer(state, {
|
|
294
|
+
type: 'DESTROY_SESSION',
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(nextState.activeFormUuid).toBeNull();
|
|
298
|
+
expect(nextState.forms).toEqual({});
|
|
299
|
+
expect(nextState.formDestroyed).toBe(true);
|
|
300
|
+
expect(mockNavigate).toHaveBeenCalledWith({ to: '${openmrsSpaBase}/forms' });
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -365,7 +365,7 @@ const reducer = (state, action) => {
|
|
|
365
365
|
[state.activeFormUuid]: {
|
|
366
366
|
...state.forms[state.activeFormUuid],
|
|
367
367
|
activeEncounterUuid: null,
|
|
368
|
-
|
|
368
|
+
activeVisitUuid: null,
|
|
369
369
|
activePatientUuid: null,
|
|
370
370
|
activeSessionUuid: null,
|
|
371
371
|
workflowState: 'REVIEW',
|
package/src/declarations.d.ts
CHANGED