@openmrs/esm-form-builder-app 3.0.2-pre.1670 → 3.0.2-pre.1673

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.
@@ -1866,7 +1866,7 @@
1866
1866
  "auxiliaryFiles": [
1867
1867
  "9976.js.map"
1868
1868
  ],
1869
- "hash": "bbd69ee1f05b2b14",
1869
+ "hash": "d89941876ef5950e",
1870
1870
  "childrenByOrder": {}
1871
1871
  }
1872
1872
  ]
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"}],"version":"3.0.2-pre.1670"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"}],"version":"3.0.2-pre.1673"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-form-builder-app",
3
- "version": "3.0.2-pre.1670",
3
+ "version": "3.0.2-pre.1673",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Form Builder for O3",
6
6
  "browser": "dist/openmrs-esm-form-builder-app.js",
@@ -45,7 +45,7 @@ const Question: React.FC<QuestionProps> = ({ checkIfQuestionIdExists }) => {
45
45
  },
46
46
  [setFormField],
47
47
  );
48
-
48
+
49
49
  const handleQuestionTypeChange = useCallback(
50
50
  (event: React.ChangeEvent<HTMLSelectElement>) => {
51
51
  const newQuestionType = event.target.value;
@@ -1,73 +1,338 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { render, screen, within } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
- import type { FormField } from '@openmrs/esm-form-engine-lib';
5
- import { FormFieldProvider } from '../../form-field-context';
6
4
  import Question from './question.component';
5
+ import { FormFieldProvider } from '../../form-field-context';
6
+ import type { FormField } from '@openmrs/esm-form-engine-lib';
7
+ import { renderingTypes } from '@constants';
7
8
 
8
- const mockSetFormField = jest.fn();
9
- const checkIfQuestionIdExists = jest.fn();
10
-
11
- const formField: FormField = {
9
+ const initialFormField: FormField = {
10
+ id: 'testId',
11
+ label: 'Test Label',
12
12
  type: 'obs',
13
- questionOptions: { rendering: 'text' },
14
- id: '1',
13
+ questionOptions: {
14
+ rendering: 'text',
15
+ },
15
16
  };
16
17
 
17
- jest.mock('../../form-field-context', () => ({
18
- ...jest.requireActual('../../form-field-context'),
19
- useFormField: () => ({ formField, setFormField: mockSetFormField }),
20
- }));
18
+ const checkIfQuestionIdExists = jest.fn(() => false);
19
+
20
+ const renderWithFormFieldProvider = (
21
+ component: React.ReactElement,
22
+ { formField = initialFormField, selectedConcept = null } = {},
23
+ ) => {
24
+ return render(
25
+ <FormFieldProvider initialFormField={formField} selectedConcept={selectedConcept}>
26
+ {component}
27
+ </FormFieldProvider>,
28
+ );
29
+ };
21
30
 
22
31
  describe('Question Component', () => {
23
- it('should render the toggles for question info', () => {
24
- renderQuestionComponent();
32
+ it('should render all required fields', () => {
33
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />);
34
+
35
+ expect(screen.getByRole('textbox', { name: /question id/i })).toBeInTheDocument();
36
+
37
+ expect(screen.getByLabelText(/question type/i)).toBeInTheDocument();
38
+
39
+ expect(screen.getByLabelText(/rendering type/i)).toBeInTheDocument();
40
+ });
25
41
 
26
- expect(
27
- screen.getByRole('group', { name: /would you like to provide additional details about the question?/i }),
28
- ).toBeInTheDocument();
29
- expect(screen.getByRole('radio', { name: /Yes/i })).toBeInTheDocument();
30
- expect(screen.getByRole('radio', { name: /No/i })).toBeInTheDocument();
42
+ it('should display the form field id in the question id input', () => {
43
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />);
44
+ const idInput = screen.getByRole('textbox', { name: /question id/i }) as HTMLInputElement;
45
+ expect(idInput).toHaveValue('testId');
31
46
  });
32
47
 
33
- it('shows TextInput when "Yes" is selected', async () => {
48
+ it('should update form field when question id changes', async () => {
34
49
  const user = userEvent.setup();
35
- renderQuestionComponent();
50
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />);
51
+ const idInput = screen.getByRole('textbox', { name: /question id/i });
36
52
 
37
- const yesRadio = screen.getByRole('radio', { name: /Yes/i });
38
- await user.click(yesRadio);
53
+ await user.clear(idInput);
54
+ await user.type(idInput, 'newTestId');
39
55
 
40
- expect(screen.getByLabelText(/additional question info/i)).toBeInTheDocument();
56
+ expect(idInput).toHaveValue('newTestId');
57
+ });
58
+
59
+ it('should validate duplicate question ids', () => {
60
+ const duplicateCheckFn = jest.fn().mockReturnValue(true);
61
+
62
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={duplicateCheckFn} />);
63
+
64
+ duplicateCheckFn.mockClear();
65
+
66
+ const idInput = screen.getByRole('textbox', { name: /question id/i });
67
+ idInput.focus();
68
+ idInput.blur();
69
+
70
+ expect(screen.getByText(/this question id already exists/i)).toBeInTheDocument();
71
+ });
72
+
73
+ it('should convert label to camel case when button is clicked', async () => {
74
+ const user = userEvent.setup();
75
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
76
+ formField: { ...initialFormField, label: 'Test Label For Camel Case' },
77
+ });
78
+
79
+ const convertButton = screen.getByText(/convert label to camel-case/i);
80
+ await user.click(convertButton);
81
+
82
+ const idInput = screen.getByRole('textbox', { name: /question id/i }) as HTMLInputElement;
83
+ expect(idInput).toHaveValue('testLabelForCamelCase');
41
84
  });
42
85
 
43
- it('hides TextInput when "No" is selected', async () => {
86
+ it('should toggle question info visibility', async () => {
44
87
  const user = userEvent.setup();
45
- renderQuestionComponent();
88
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />);
46
89
 
47
- const noRadio = screen.getByRole('radio', { name: /No/i });
90
+ expect(screen.queryByLabelText(/additional question info/i)).not.toBeInTheDocument();
91
+
92
+ const yesRadio = screen.getByLabelText(/Yes/i);
93
+ await user.click(yesRadio);
94
+
95
+ expect(screen.getByLabelText(/additional question info/i)).toBeInTheDocument();
96
+
97
+ const noRadio = screen.getByLabelText(/No/i);
48
98
  await user.click(noRadio);
49
99
 
50
100
  expect(screen.queryByLabelText(/additional question info/i)).not.toBeInTheDocument();
51
101
  });
52
102
 
53
- it('updates questionInfo state when user types in TextInput', async () => {
103
+ it('should update question info when input changes', async () => {
54
104
  const user = userEvent.setup();
55
- renderQuestionComponent();
105
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
106
+ formField: { ...initialFormField, questionInfo: 'Initial info' },
107
+ });
56
108
 
57
- const yesRadio = screen.getByRole('radio', { name: /Yes/i });
58
- await user.click(yesRadio);
109
+ const questionInfoInput = screen.getByLabelText(/additional question info/i);
110
+ await user.clear(questionInfoInput);
111
+ await user.type(questionInfoInput, 'New question info');
112
+
113
+ expect(questionInfoInput).toHaveValue('New question info');
114
+ });
115
+
116
+ it('should toggle question required status', async () => {
117
+ const user = userEvent.setup();
118
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />);
119
+
120
+ const optionalRadio = screen.getByLabelText(/optional/i);
121
+ const requiredRadio = screen.getByLabelText(/required/i);
122
+
123
+ expect(optionalRadio).toBeChecked();
124
+ expect(requiredRadio).not.toBeChecked();
59
125
 
60
- const textInput = screen.getByLabelText(/additional question info/i);
61
- await user.type(textInput, 'New question info');
126
+ await user.click(requiredRadio);
62
127
 
63
- expect(textInput).toHaveValue('New question info');
128
+ expect(requiredRadio).toBeChecked();
129
+ expect(optionalRadio).not.toBeChecked();
130
+
131
+ await user.click(optionalRadio);
132
+
133
+ expect(optionalRadio).toBeChecked();
134
+ expect(requiredRadio).not.toBeChecked();
64
135
  });
65
- });
66
136
 
67
- function renderQuestionComponent() {
68
- render(
69
- <FormFieldProvider initialFormField={formField}>
70
- <Question checkIfQuestionIdExists={checkIfQuestionIdExists} />
71
- </FormFieldProvider>,
72
- );
73
- }
137
+ it('should show only date and datetime rendering types for encounterDatetime question type', async () => {
138
+ const user = userEvent.setup();
139
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
140
+ formField: { ...initialFormField, type: 'encounterDatetime' },
141
+ });
142
+
143
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
144
+ expect(renderingTypeSelect).toBeInTheDocument();
145
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('encounterDatetime');
146
+
147
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
148
+ (option) => option.value && option.value !== '',
149
+ );
150
+
151
+ expect(options).toHaveLength(2);
152
+ expect(options[0]).toHaveTextContent('date');
153
+ expect(options[1]).toHaveTextContent('datetime');
154
+ });
155
+
156
+ it('should show only text and markdown rendering types for control question type', async () => {
157
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
158
+ formField: { ...initialFormField, type: 'control' },
159
+ });
160
+
161
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
162
+ expect(renderingTypeSelect).toBeInTheDocument();
163
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('control');
164
+
165
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
166
+ (option) => option.value && option.value !== '',
167
+ );
168
+
169
+ expect(options).toHaveLength(2);
170
+ expect(options[0]).toHaveTextContent('text');
171
+ expect(options[1]).toHaveTextContent('markdown');
172
+ });
173
+
174
+ it('should show only ui-select-extended rendering type for encounterLocation question type', async () => {
175
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
176
+ formField: { ...initialFormField, type: 'encounterLocation' },
177
+ });
178
+
179
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
180
+ expect(renderingTypeSelect).toBeInTheDocument();
181
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('encounterLocation');
182
+
183
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
184
+ (option) => option.value && option.value !== '',
185
+ );
186
+
187
+ expect(options).toHaveLength(1);
188
+ expect(options[0]).toHaveTextContent('ui-select-extended');
189
+ });
190
+
191
+ it('should show only ui-select-extended rendering type for encounterProvider question type', async () => {
192
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
193
+ formField: { ...initialFormField, type: 'encounterProvider' },
194
+ });
195
+
196
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
197
+ expect(renderingTypeSelect).toBeInTheDocument();
198
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('encounterProvider');
199
+
200
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
201
+ (option) => option.value && option.value !== '',
202
+ );
203
+
204
+ expect(options).toHaveLength(1);
205
+ expect(options[0]).toHaveTextContent('ui-select-extended');
206
+ });
207
+
208
+ it('should show only ui-select-extended rendering type for encounterRole question type', async () => {
209
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
210
+ formField: { ...initialFormField, type: 'encounterRole' },
211
+ });
212
+
213
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
214
+ expect(renderingTypeSelect).toBeInTheDocument();
215
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('encounterRole');
216
+
217
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
218
+ (option) => option.value && option.value !== '',
219
+ );
220
+
221
+ expect(options).toHaveLength(1);
222
+ expect(options[0]).toHaveTextContent('ui-select-extended');
223
+ });
224
+
225
+ it('should show only group and repeating rendering types for obsGroup question type', async () => {
226
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
227
+ formField: { ...initialFormField, type: 'obsGroup' },
228
+ });
229
+
230
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
231
+ expect(renderingTypeSelect).toBeInTheDocument();
232
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('obsGroup');
233
+
234
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
235
+ (option) => option.value && option.value !== '',
236
+ );
237
+
238
+ expect(options).toHaveLength(2);
239
+ expect(options[0]).toHaveTextContent('group');
240
+ expect(options[1]).toHaveTextContent('repeating');
241
+ });
242
+
243
+ it('should show only group and repeating rendering types for testOrder question type', async () => {
244
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
245
+ formField: { ...initialFormField, type: 'testOrder' },
246
+ });
247
+
248
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
249
+ expect(renderingTypeSelect).toBeInTheDocument();
250
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('testOrder');
251
+
252
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
253
+ (option) => option.value && option.value !== '',
254
+ );
255
+
256
+ expect(options).toHaveLength(2);
257
+ expect(options[0]).toHaveTextContent('group');
258
+ expect(options[1]).toHaveTextContent('repeating');
259
+ });
260
+
261
+ it('should show only text rendering type for patientIdentifier question type', async () => {
262
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
263
+ formField: { ...initialFormField, type: 'patientIdentifier' },
264
+ });
265
+
266
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
267
+ expect(renderingTypeSelect).toBeInTheDocument();
268
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('patientIdentifier');
269
+
270
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
271
+ (option) => option.value && option.value !== '',
272
+ );
273
+
274
+ expect(options).toHaveLength(1);
275
+ expect(options[0]).toHaveTextContent('text');
276
+ });
277
+
278
+ it('should show only select rendering type for programState question type', async () => {
279
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
280
+ formField: { ...initialFormField, type: 'programState' },
281
+ });
282
+
283
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
284
+ expect(renderingTypeSelect).toBeInTheDocument();
285
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('programState');
286
+
287
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
288
+ (option) => option.value && option.value !== '',
289
+ );
290
+
291
+ expect(options).toHaveLength(1);
292
+ expect(options[0]).toHaveTextContent('select');
293
+ });
294
+
295
+ it('should show all rendering types for obs question type', async () => {
296
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
297
+ formField: { ...initialFormField, type: 'obs' },
298
+ });
299
+
300
+ const renderingTypeSelect = screen.getByLabelText(/rendering type/i);
301
+ expect(renderingTypeSelect).toBeInTheDocument();
302
+ expect(screen.getByLabelText(/question type/i)).toHaveValue('obs');
303
+
304
+ const options = (within(renderingTypeSelect).getAllByRole('option') as HTMLOptionElement[]).filter(
305
+ (option) => option.value && option.value !== '',
306
+ );
307
+
308
+ expect(options).toHaveLength(renderingTypes.length);
309
+
310
+ const optionTexts = options.map((option) => option.textContent);
311
+
312
+ renderingTypes.forEach((renderType) => {
313
+ expect(optionTexts).toContain(renderType);
314
+ });
315
+ });
316
+
317
+ it('should load question info from the form field object', () => {
318
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
319
+ formField: { ...initialFormField, questionInfo: 'Initial question info' },
320
+ });
321
+
322
+ expect(screen.getByLabelText(/additional question info/i)).toBeInTheDocument();
323
+ expect(screen.getByLabelText(/additional question info/i)).toHaveValue('Initial question info');
324
+ });
325
+
326
+ it('should not show label and required inputs when rendering type is markdown', () => {
327
+ renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, {
328
+ formField: {
329
+ ...initialFormField,
330
+ questionOptions: {
331
+ rendering: 'markdown',
332
+ },
333
+ },
334
+ });
335
+ expect(screen.queryByLabelText(/^label$/i)).not.toBeInTheDocument();
336
+ expect(screen.queryByText(/is this question a required or optional field/i)).not.toBeInTheDocument();
337
+ });
338
+ });