@openmrs/esm-form-engine-lib 2.1.0-pre.1511 → 2.1.0-pre.1517

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.
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "Sample Unspecified Form",
3
+ "pages": [
4
+ {
5
+ "label": "Page 1",
6
+ "sections": [
7
+ {
8
+ "label": "Section 1",
9
+ "isExpanded": "true",
10
+ "questions": [
11
+ {
12
+ "label": "Body Weight",
13
+ "type": "obs",
14
+ "questionOptions": {
15
+ "rendering": "number",
16
+ "concept": "560555AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
17
+ "conceptMappings": [
18
+ {
19
+ "type": "CIEL",
20
+ "value": "160555"
21
+ }
22
+ ]
23
+ },
24
+ "id": "bodyWeight",
25
+ "validators": [],
26
+ "required": true,
27
+ "unspecified": true
28
+ }
29
+ ]
30
+ }
31
+ ]
32
+ }
33
+ ],
34
+ "availableIntents": [],
35
+ "processor": "EncounterFormProcessor",
36
+ "uuid": "na24c540-cc83-43bc-978f-c1ef180a597f",
37
+ "referencedForms": [],
38
+ "encounterType": "b9c1f50f-f77d-42e2-ad2a-d29304dde2fv"
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-engine-lib",
3
- "version": "2.1.0-pre.1511",
3
+ "version": "2.1.0-pre.1517",
4
4
  "description": "React Form Engine for O3",
5
5
  "browser": "dist/openmrs-esm-form-engine-lib.js",
6
6
  "main": "src/index.ts",
@@ -104,7 +104,7 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
104
104
  initialSelectedItems={initiallySelectedQuestionItems}
105
105
  label={''}
106
106
  titleText={label}
107
- key={counter}
107
+ key={field.id}
108
108
  itemToString={(item) => (item ? item.label : ' ')}
109
109
  disabled={field.isDisabled}
110
110
  invalid={errors.length > 0}
@@ -118,10 +118,10 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
118
118
  {field.questionOptions.answers?.map((value, index) => {
119
119
  return (
120
120
  <Checkbox
121
- key={value.concept}
121
+ key={`${field.id}-${value.concept}`}
122
122
  className={styles.checkbox}
123
123
  labelText={value.label}
124
- id={value.concept}
124
+ id={`${field.id}-${value.concept}`}
125
125
  onChange={() => {
126
126
  handleSelectCheckbox(value);
127
127
  }}
@@ -7,7 +7,7 @@ import { isTrue } from '../../../utils/boolean-utils';
7
7
 
8
8
  import styles from './unspecified.scss';
9
9
  import { useFormProviderContext } from '../../../provider/form-provider';
10
- import { isViewMode } from '../../../utils/common-utils';
10
+ import { clearSubmission, isViewMode } from '../../../utils/common-utils';
11
11
 
12
12
  interface UnspecifiedFieldProps {
13
13
  field: FormField;
@@ -29,25 +29,28 @@ const UnspecifiedField: React.FC<UnspecifiedFieldProps> = ({ field, fieldValue,
29
29
  }, []);
30
30
 
31
31
  useEffect(() => {
32
- if (field.meta.submission?.unspecified && field.meta.submission.newValue) {
32
+ if (field.meta.submission?.unspecified && (field.meta.submission.newValue || !isEmpty(fieldValue))) {
33
33
  setIsUnspecified(false);
34
34
  field.meta.submission.unspecified = false;
35
35
  updateFormField(field);
36
36
  }
37
- }, [field.meta?.submission]);
37
+ }, [field.meta?.submission, fieldValue]);
38
38
 
39
39
  const handleOnChange = useCallback(
40
40
  (value) => {
41
41
  const rendering = field.questionOptions.rendering;
42
42
  if (value.target.checked) {
43
- const emptyValue = rendering === 'checkbox' ? [] : '';
44
- field.meta.submission = { ...field.meta.submission, unspecified: true };
45
- updateFormField({ ...field });
46
43
  setIsUnspecified(true);
44
+ const emptyValue = rendering === 'checkbox' ? [] : '';
45
+ clearSubmission(field);
46
+ field.meta.submission.unspecified = true;
47
+ updateFormField(field);
47
48
  setFieldValue(emptyValue);
48
49
  onAfterChange(emptyValue);
49
50
  } else {
50
51
  setIsUnspecified(false);
52
+ field.meta.submission.unspecified = false;
53
+ updateFormField(field);
51
54
  }
52
55
  },
53
56
  [field.questionOptions.rendering],
@@ -1,74 +1,166 @@
1
1
  import React from 'react';
2
- import dayjs from 'dayjs';
3
- import { fireEvent, render, screen } from '@testing-library/react';
4
- import { OpenmrsDatePicker } from '@openmrs/esm-framework';
5
- import { type FormField } from '../../../types';
6
- import { findTextOrDateInput } from '../../../utils/test-utils';
7
-
8
- const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker);
9
-
10
- mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange }) => {
11
- return (
12
- <>
13
- <label htmlFor={id}>{labelText}</label>
14
- <input
15
- id={id}
16
- value={value ? dayjs(value.toString()).format('DD/MM/YYYY') : undefined}
17
- onChange={(evt) => onChange(new Date(evt.target.value))}
18
- />
19
- </>
20
- );
2
+ import { act, render, screen } from '@testing-library/react';
3
+ import { usePatient, useSession } from '@openmrs/esm-framework';
4
+ import { type FormSchema, type SessionMode } from '../../../types';
5
+ import { findNumberInput } from '../../../utils/test-utils';
6
+ import unspecifiedForm from '../../../../__mocks__/forms/rfe-forms/sample_unspecified-form.json';
7
+ import { FormEngine } from '../../..';
8
+ import { mockPatient } from '../../../../__mocks__/patient.mock';
9
+ import { mockSessionDataResponse } from '../../../../__mocks__/session.mock';
10
+ import userEvent from '@testing-library/user-event';
11
+ import * as api from '../../../api';
12
+
13
+ const mockUsePatient = jest.mocked(usePatient);
14
+ const mockUseSession = jest.mocked(useSession);
15
+
16
+ global.ResizeObserver = require('resize-observer-polyfill');
17
+
18
+ jest.mock('../../../api', () => {
19
+ const originalModule = jest.requireActual('../../../api');
20
+ return {
21
+ ...originalModule,
22
+ getPreviousEncounter: jest.fn().mockImplementation(() => Promise.resolve(null)),
23
+ getConcept: jest.fn().mockImplementation(() => Promise.resolve(null)),
24
+ saveEncounter: jest.fn(),
25
+ };
21
26
  });
22
27
 
23
- const question: FormField = {
24
- label: 'Visit Date',
25
- type: 'obs',
26
- datePickerFormat: 'calendar',
27
- questionOptions: {
28
- rendering: 'date',
29
- concept: '163260AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
30
- },
31
- id: 'visit-date',
32
- };
28
+ jest.mock('../../../hooks/useConcepts', () => ({
29
+ useConcepts: jest.fn().mockImplementation((references: Set<string>) => {
30
+ return {
31
+ isLoading: false,
32
+ concepts: [],
33
+ error: undefined,
34
+ };
35
+ }),
36
+ }));
33
37
 
34
- const renderForm = (initialValues) => {
35
- render(<></>);
38
+ jest.mock('../../../hooks/useEncounterRole', () => ({
39
+ useEncounterRole: jest.fn().mockReturnValue({
40
+ isLoading: false,
41
+ encounterRole: { name: 'Clinician', uuid: 'clinician-uuid' },
42
+ error: undefined,
43
+ }),
44
+ }));
45
+
46
+ jest.mock('../../../hooks/useEncounter', () => ({
47
+ useEncounter: jest.fn().mockImplementation((formJson: FormSchema) => {
48
+ return {
49
+ encounter: formJson.encounter
50
+ ? {
51
+ uuid: 'encounter-uuid',
52
+ obs: [],
53
+ }
54
+ : null,
55
+ isLoading: false,
56
+ error: undefined,
57
+ };
58
+ }),
59
+ }));
60
+
61
+ const renderForm = async (mode: SessionMode = 'enter') => {
62
+ await act(async () => {
63
+ render(
64
+ <FormEngine
65
+ formJson={unspecifiedForm as FormSchema}
66
+ patientUUID="8673ee4f-e2ab-4077-ba55-4980f408773e"
67
+ mode={mode}
68
+ encounterUUID={mode === 'edit' ? 'encounter-uuid' : null}
69
+ />,
70
+ );
71
+ });
36
72
  };
37
73
 
38
- describe.skip('Unspecified', () => {
39
- it('Should toggle the "Unspecified" checkbox on click', async () => {
40
- // setup
41
- renderForm({});
74
+ describe('Unspecified', () => {
75
+ const user = userEvent.setup();
76
+
77
+ beforeEach(() => {
78
+ Object.defineProperty(window, 'i18next', {
79
+ writable: true,
80
+ configurable: true,
81
+ value: {
82
+ language: 'en',
83
+ t: jest.fn(),
84
+ },
85
+ });
86
+
87
+ mockUsePatient.mockImplementation(() => ({
88
+ patient: mockPatient,
89
+ isLoading: false,
90
+ error: undefined,
91
+ patientUuid: mockPatient.id,
92
+ }));
93
+
94
+ mockUseSession.mockImplementation(() => mockSessionDataResponse.data);
95
+ });
96
+
97
+ it('Should clear field value when the "Unspecified" checkbox is clicked', async () => {
98
+ //setup
99
+ await renderForm();
42
100
  const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
101
+ const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
43
102
 
44
103
  // assert initial state
45
104
  expect(unspecifiedCheckbox).not.toBeChecked();
105
+ expect(bodyWeightField.value).toBe('');
46
106
 
47
- // assert checked
48
- fireEvent.click(unspecifiedCheckbox);
49
- expect(unspecifiedCheckbox).toBeChecked();
107
+ await user.type(bodyWeightField, '55');
50
108
 
51
- // assert unchecked
52
- fireEvent.click(unspecifiedCheckbox);
53
- expect(unspecifiedCheckbox).not.toBeChecked();
109
+ // assert new value
110
+ expect(bodyWeightField.value).toBe('55');
111
+
112
+ // mark as unspecified
113
+ await user.click(unspecifiedCheckbox);
114
+ expect(unspecifiedCheckbox).toBeChecked();
115
+ expect(bodyWeightField.value).toBe('');
54
116
  });
55
117
 
56
- it('Should clear field value when the "Unspecified" checkbox is clicked', async () => {
118
+ it('Should bypass form validation when the "Unspecified" checkbox is clicked', async () => {
57
119
  //setup
58
- renderForm({});
120
+ const mockSaveEncounter = jest.spyOn(api, 'saveEncounter');
121
+ await renderForm();
59
122
  const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
60
- const visitDateField = await findTextOrDateInput(screen, 'Visit Date');
123
+ const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
61
124
 
62
125
  // assert initial state
63
126
  expect(unspecifiedCheckbox).not.toBeChecked();
64
- expect(visitDateField.value).toBe('');
127
+ expect(bodyWeightField.value).toBe('');
128
+
129
+ // attempt to submit the form
130
+ await user.click(screen.getByRole('button', { name: /Save/ }));
131
+ expect(screen.getByText(/Field is mandatory/)).toBeInTheDocument();
132
+ expect(mockSaveEncounter).not.toHaveBeenCalled();
133
+
134
+ // mark as unspecified
135
+ await user.click(unspecifiedCheckbox);
136
+ expect(unspecifiedCheckbox).toBeChecked();
137
+ expect(bodyWeightField.value).toBe('');
138
+
139
+ // submit the form again
140
+ await user.click(screen.getByRole('button', { name: /Save/ }));
141
+ expect(mockSaveEncounter).toHaveBeenCalled();
142
+ });
65
143
 
66
- fireEvent.change(visitDateField, { target: { value: '2023-09-09T00:00:00.000Z' } });
144
+ it('Should mark fields with null values as unspecified when in edit mode', async () => {
145
+ // setup
146
+ await renderForm('edit');
147
+ const unspecifiedCheckbox = screen.getByRole('checkbox', { name: /Unspecified/ });
148
+ const bodyWeightField = await findNumberInput(screen, 'Body Weight *');
67
149
 
68
- // assert checked
69
- fireEvent.click(unspecifiedCheckbox);
150
+ // assert initial state
70
151
  expect(unspecifiedCheckbox).toBeChecked();
71
- //TODO : Fix this test case - - https://openmrs.atlassian.net/browse/O3-3479s
72
- // expect(visitDateField.value).toBe('');
152
+ expect(bodyWeightField.value).toBe('');
153
+ });
154
+
155
+ it('Should not display the unspecified checkbox in view mode', async () => {
156
+ // setup
157
+ await renderForm('view');
158
+
159
+ try {
160
+ screen.getByRole('checkbox', { name: /Unspecified/ });
161
+ fail('Unspecified checkbox should not be displayed');
162
+ } catch (error) {
163
+ expect(error).toBeDefined();
164
+ }
73
165
  });
74
166
  });
@@ -105,6 +105,10 @@ export const FormFieldRenderer = ({ fieldId, valueAdapter, repeatOptions }: Form
105
105
  if (field.meta.submission?.warnings) {
106
106
  setWarnings(field.meta.submission.warnings);
107
107
  }
108
+ if (field.meta.submission?.unspecified) {
109
+ setErrors([]);
110
+ removeInvalidField(field.id);
111
+ }
108
112
  }, [field.meta.submission]);
109
113
 
110
114
  const onAfterChange = (value: any) => {
@@ -30,6 +30,7 @@ export function clearSubmission(field: FormField) {
30
30
  field.meta = { ...(field.meta || {}), submission: {} };
31
31
  }
32
32
  field.meta.submission = {
33
+ ...field.meta.submission,
33
34
  voidedValue: null,
34
35
  newValue: null,
35
36
  };