@openmrs/esm-form-engine-lib 3.1.3-pre.1757 → 3.1.3-pre.1764
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__/forms/rfe-forms/default-values-form.json +91 -0
- package/package.json +1 -1
- package/src/components/inputs/markdown/markdown.component.tsx +5 -1
- package/src/components/inputs/multi-select/multi-select.component.tsx +2 -2
- package/src/form-engine.test.tsx +43 -2
- package/src/processors/encounter/encounter-form-processor.ts +3 -0
- package/src/processors/encounter/encounter-processor-helper.ts +9 -2
@@ -0,0 +1,91 @@
|
|
1
|
+
{
|
2
|
+
"encounterType": "",
|
3
|
+
"name": "Sample Default Values Form",
|
4
|
+
"processor": "EncounterFormProcessor",
|
5
|
+
"referencedForms": [],
|
6
|
+
"uuid": "3620c971-58e5-4e70-83ce-98d0dcf4bb7b",
|
7
|
+
"version": "1.0",
|
8
|
+
"pages": [
|
9
|
+
{
|
10
|
+
"label": "First Page",
|
11
|
+
"sections": [
|
12
|
+
{
|
13
|
+
"label": "A Section",
|
14
|
+
"isExpanded": "true",
|
15
|
+
"questions": [
|
16
|
+
{
|
17
|
+
"id": "sampleQuestion",
|
18
|
+
"label": "Text field with Default Value",
|
19
|
+
"type": "obs",
|
20
|
+
"questionOptions": {
|
21
|
+
"rendering": "text",
|
22
|
+
"defaultValue": "Value text",
|
23
|
+
"concept": "f82ba2b7-3849-4ad0-b867-36881e59f5c8"
|
24
|
+
}
|
25
|
+
}
|
26
|
+
]
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"label": "Another Section",
|
30
|
+
"isExpanded": "true",
|
31
|
+
"questions": [
|
32
|
+
{
|
33
|
+
"id": "codedQuestion",
|
34
|
+
"label": "Dropdown with Default Value",
|
35
|
+
"type": "obs",
|
36
|
+
"questionOptions": {
|
37
|
+
"rendering": "select",
|
38
|
+
"concept": "8cdea80a-d167-431c-8278-246c7a1f913b",
|
39
|
+
"defaultValue": "6b4e859c-86ca-41e5-b1c4-017889653b59",
|
40
|
+
"answers": [
|
41
|
+
{
|
42
|
+
"concept": "2b4e859c-86ca-41e5-b1c4-017889653b50",
|
43
|
+
"label": "Choice 1",
|
44
|
+
"conceptMappings": []
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"concept": "6b4e859c-86ca-41e5-b1c4-017889653b59",
|
48
|
+
"label": "Choice 2",
|
49
|
+
"conceptMappings": []
|
50
|
+
},
|
51
|
+
{
|
52
|
+
"concept": "5b4e859c-c6ca-41e5-b1c4-017889653b5k",
|
53
|
+
"label": "Choice 3",
|
54
|
+
"conceptMappings": []
|
55
|
+
}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"id": "codedQuestionWithInvalidDefaultValue",
|
61
|
+
"label": "Dropdown with an invalid Default Value",
|
62
|
+
"type": "obs",
|
63
|
+
"questionOptions": {
|
64
|
+
"rendering": "select",
|
65
|
+
"concept": "8cdea80a-d167-431c-8278-246c7a1f913b",
|
66
|
+
"defaultValue": "invalid-value",
|
67
|
+
"answers": [
|
68
|
+
{
|
69
|
+
"concept": "2b4e859c-86ca-41e5-b1c4-017889653b50",
|
70
|
+
"label": "Choice 1",
|
71
|
+
"conceptMappings": []
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"concept": "6b4e859c-86ca-41e5-b1c4-017889653b59",
|
75
|
+
"label": "Choice 2",
|
76
|
+
"conceptMappings": []
|
77
|
+
},
|
78
|
+
{
|
79
|
+
"concept": "5b4e859c-c6ca-41e5-b1c4-017889653b5k",
|
80
|
+
"label": "Choice 3",
|
81
|
+
"conceptMappings": []
|
82
|
+
}
|
83
|
+
]
|
84
|
+
}
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
88
|
+
]
|
89
|
+
}
|
90
|
+
]
|
91
|
+
}
|
package/package.json
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import { useTranslation } from 'react-i18next';
|
2
3
|
import MarkdownWrapper from './markdown-wrapper.component';
|
3
4
|
import { type FormFieldInputProps } from '../../../types';
|
4
5
|
|
5
6
|
const Markdown: React.FC<FormFieldInputProps> = ({ field }) => {
|
6
|
-
|
7
|
+
const { t } = useTranslation();
|
8
|
+
return !field.isHidden && <MarkdownWrapper
|
9
|
+
markdown={t(field.value, { defaultValue: field.value, interpolation: { escapeValue: false } })}
|
10
|
+
/>;
|
7
11
|
};
|
8
12
|
export default Markdown;
|
@@ -101,7 +101,7 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
|
|
101
101
|
initialSelectedItems={initiallySelectedQuestionItems}
|
102
102
|
label={''}
|
103
103
|
titleText={<FieldLabel field={field} />}
|
104
|
-
itemToString={(item) => (item ? item.label : ' ')}
|
104
|
+
itemToString={(item) => (item ? t(item.label) : ' ')}
|
105
105
|
disabled={field.isDisabled}
|
106
106
|
invalid={errors.length > 0}
|
107
107
|
invalidText={errors[0]?.message}
|
@@ -143,7 +143,7 @@ const MultiSelect: React.FC<FormFieldInputProps> = ({ field, value, errors, warn
|
|
143
143
|
<div className={styles.tagContainer}>
|
144
144
|
{formFieldAdapters[field.type]?.getDisplayValue(field, value)?.map((displayValue, index) => (
|
145
145
|
<Tag key={index} type="cool-gray">
|
146
|
-
{displayValue}
|
146
|
+
{t(displayValue)}
|
147
147
|
</Tag>
|
148
148
|
))}
|
149
149
|
</div>
|
package/src/form-engine.test.tsx
CHANGED
@@ -46,6 +46,7 @@ import readOnlyValidationForm from '__mocks__/forms/rfe-forms/read-only-validati
|
|
46
46
|
import jsExpressionValidationForm from '__mocks__/forms/rfe-forms/js-expression-validation-form.json';
|
47
47
|
import hidePagesAndSectionsForm from '__mocks__/forms/rfe-forms/hide-pages-and-sections-form.json';
|
48
48
|
import diagnosisForm from '__mocks__/forms/rfe-forms/diagnosis-test-form.json';
|
49
|
+
import defaultValuesForm from '__mocks__/forms/rfe-forms/default-values-form.json';
|
49
50
|
|
50
51
|
import FormEngine from './form-engine.component';
|
51
52
|
import { type FormSchema, type OpenmrsEncounter, type SessionMode } from './types';
|
@@ -358,7 +359,7 @@ describe('Form engine component', () => {
|
|
358
359
|
await act(async () => {
|
359
360
|
renderForm(null, requiredTestForm);
|
360
361
|
});
|
361
|
-
|
362
|
+
|
362
363
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
363
364
|
|
364
365
|
const labels = screen.getAllByText(/Text question/i);
|
@@ -457,7 +458,9 @@ describe('Form engine component', () => {
|
|
457
458
|
expect(selectErrorMessage).toBeInTheDocument();
|
458
459
|
|
459
460
|
// Validate multi-select field
|
460
|
-
const multiSelectInputField = screen.getByText('If Unscheduled, actual scheduled reason multi-select', {
|
461
|
+
const multiSelectInputField = screen.getByText('If Unscheduled, actual scheduled reason multi-select', {
|
462
|
+
exact: true,
|
463
|
+
});
|
461
464
|
expect(multiSelectInputField).toBeInTheDocument();
|
462
465
|
const multiSelectErrorMessage = screen.getByText(
|
463
466
|
'Patient visit marked as unscheduled. Please provide the scheduled multi-select reason.',
|
@@ -699,6 +702,44 @@ describe('Form engine component', () => {
|
|
699
702
|
});
|
700
703
|
});
|
701
704
|
|
705
|
+
describe('Default values', () => {
|
706
|
+
it('should initialize fields with default values', async () => {
|
707
|
+
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
708
|
+
|
709
|
+
await act(async () => renderForm(null, defaultValuesForm));
|
710
|
+
|
711
|
+
// text field
|
712
|
+
const textField = await findTextOrDateInput(screen, 'Text field with Default Value');
|
713
|
+
expect(textField).toHaveValue('Value text');
|
714
|
+
|
715
|
+
// dropdown field
|
716
|
+
const dropdownField = await findSelectInput(screen, 'Dropdown with Default Value');
|
717
|
+
expect(dropdownField.title).toBe('Choice 2');
|
718
|
+
|
719
|
+
// dropdown with an invalid default value
|
720
|
+
const invalidDropdownField = await findSelectInput(screen, 'Dropdown with an invalid Default Value');
|
721
|
+
expect(invalidDropdownField.title).toBe('Choose an option');
|
722
|
+
|
723
|
+
await user.click(screen.getByRole('button', { name: /save/i }));
|
724
|
+
|
725
|
+
const encounter = saveEncounterMock.mock.calls[0][1];
|
726
|
+
expect(encounter.obs).toEqual([
|
727
|
+
{
|
728
|
+
value: 'Value text',
|
729
|
+
concept: 'f82ba2b7-3849-4ad0-b867-36881e59f5c8',
|
730
|
+
formFieldNamespace: 'rfe-forms',
|
731
|
+
formFieldPath: 'rfe-forms-sampleQuestion',
|
732
|
+
},
|
733
|
+
{
|
734
|
+
value: '6b4e859c-86ca-41e5-b1c4-017889653b59',
|
735
|
+
concept: '8cdea80a-d167-431c-8278-246c7a1f913b',
|
736
|
+
formFieldNamespace: 'rfe-forms',
|
737
|
+
formFieldPath: 'rfe-forms-codedQuestion',
|
738
|
+
},
|
739
|
+
]);
|
740
|
+
});
|
741
|
+
});
|
742
|
+
|
702
743
|
describe('Calculated values', () => {
|
703
744
|
it('should evaluate BMI', async () => {
|
704
745
|
const saveEncounterMock = jest.spyOn(api, 'saveEncounter');
|
@@ -285,6 +285,9 @@ export class EncounterFormProcessor extends FormProcessor {
|
|
285
285
|
console.error(error);
|
286
286
|
}
|
287
287
|
}
|
288
|
+
if (field.questionOptions.defaultValue) {
|
289
|
+
initialValues[field.id] = inferInitialValueFromDefaultFieldValue(field);
|
290
|
+
}
|
288
291
|
if (field.questionOptions.calculate?.calculateExpression) {
|
289
292
|
fieldsWithCalculateExpressions.push(field);
|
290
293
|
}
|
@@ -280,10 +280,17 @@ export function inferInitialValueFromDefaultFieldValue(field: FormField) {
|
|
280
280
|
if (field.questionOptions.rendering == 'toggle' && typeof field.questionOptions.defaultValue != 'boolean') {
|
281
281
|
return field.questionOptions.defaultValue == ConceptTrue;
|
282
282
|
}
|
283
|
+
|
283
284
|
// validate default value
|
284
|
-
|
285
|
-
|
285
|
+
const errors = DefaultValueValidator.validate(field, field.questionOptions.defaultValue);
|
286
|
+
if (errors.length) {
|
287
|
+
console.error(
|
288
|
+
`Default value validation errors for field "${field.id}" with value "${field.questionOptions.defaultValue}":`,
|
289
|
+
errors,
|
290
|
+
);
|
291
|
+
return null;
|
286
292
|
}
|
293
|
+
return field.questionOptions.defaultValue;
|
287
294
|
}
|
288
295
|
|
289
296
|
export async function hydrateRepeatField(
|