@evoke-platform/ui-components 1.8.0-testing.9 → 1.8.0

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.
Files changed (40) hide show
  1. package/dist/published/components/custom/DataGrid/DataGrid.d.ts +1 -0
  2. package/dist/published/components/custom/DataGrid/DataGrid.js +2 -1
  3. package/dist/published/components/custom/DataGrid/Toolbar.d.ts +1 -0
  4. package/dist/published/components/custom/DataGrid/Toolbar.js +3 -2
  5. package/dist/published/components/custom/DataGrid/index.d.ts +1 -0
  6. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +47 -40
  7. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +1 -1
  8. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +2 -0
  9. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +2 -2
  10. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.d.ts +2 -0
  11. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +2 -2
  12. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts +1 -1
  13. package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +18 -6
  14. package/dist/published/components/custom/Form/tests/Form.test.js +192 -2
  15. package/dist/published/components/custom/Form/tests/test-data.d.ts +7 -0
  16. package/dist/published/components/custom/Form/tests/test-data.js +138 -0
  17. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -2
  18. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -2
  19. package/dist/published/components/custom/FormV2/FormRendererContainer.js +2 -2
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
  21. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +1 -2
  22. package/dist/published/components/custom/FormV2/components/utils.d.ts +1 -1
  23. package/dist/published/components/custom/FormV2/components/utils.js +1 -1
  24. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.d.ts +1 -0
  25. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +173 -0
  26. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.d.ts +1 -0
  27. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +96 -0
  28. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +16 -0
  29. package/dist/published/components/custom/FormV2/tests/test-data.js +394 -0
  30. package/dist/published/components/custom/index.d.ts +1 -0
  31. package/dist/published/index.d.ts +1 -1
  32. package/dist/published/stories/FormRenderer.stories.d.ts +7 -0
  33. package/dist/published/stories/FormRenderer.stories.js +65 -0
  34. package/dist/published/stories/FormRendererContainer.stories.d.ts +7 -0
  35. package/dist/published/stories/FormRendererContainer.stories.js +56 -0
  36. package/dist/published/stories/FormRendererData.d.ts +116 -0
  37. package/dist/published/stories/FormRendererData.js +925 -0
  38. package/dist/published/stories/sharedMswHandlers.d.ts +1 -0
  39. package/dist/published/stories/sharedMswHandlers.js +100 -0
  40. package/package.json +10 -4
@@ -39,6 +39,17 @@ export const licenseObject = {
39
39
  name: 'Create',
40
40
  type: 'create',
41
41
  outputEvent: 'License Created',
42
+ form: {
43
+ entries: [
44
+ {
45
+ parameterId: 'name',
46
+ type: 'input',
47
+ display: {
48
+ label: 'License Number',
49
+ },
50
+ },
51
+ ],
52
+ },
42
53
  },
43
54
  ],
44
55
  };
@@ -78,6 +89,118 @@ export const licenseTypeObject = {
78
89
  },
79
90
  ],
80
91
  };
92
+ // New dedicated object for 508 testing
93
+ export const accessibility508Object = {
94
+ id: 'accessibility508',
95
+ name: 'Accessibility 508 Test Object',
96
+ properties: [
97
+ {
98
+ id: 'name',
99
+ name: 'Name',
100
+ type: 'string',
101
+ },
102
+ {
103
+ id: 'license',
104
+ name: 'License',
105
+ type: 'object',
106
+ objectId: 'license',
107
+ },
108
+ {
109
+ id: 'user',
110
+ name: 'User',
111
+ type: 'user',
112
+ },
113
+ ],
114
+ actions: [
115
+ {
116
+ id: '_update1',
117
+ name: 'Update Related Object',
118
+ type: 'update',
119
+ outputEvent: '508 Test Object Updated',
120
+ parameters: [
121
+ {
122
+ id: 'name',
123
+ name: 'Name',
124
+ type: 'string',
125
+ },
126
+ {
127
+ id: 'license',
128
+ name: 'License',
129
+ type: 'object',
130
+ objectId: 'license',
131
+ },
132
+ ],
133
+ form: {
134
+ entries: [
135
+ {
136
+ parameterId: 'name',
137
+ type: 'input',
138
+ display: {
139
+ label: 'Name',
140
+ },
141
+ },
142
+ {
143
+ parameterId: 'license',
144
+ type: 'input',
145
+ display: {
146
+ label: 'License',
147
+ relatedObjectDisplay: 'dropdown',
148
+ },
149
+ },
150
+ ],
151
+ },
152
+ },
153
+ {
154
+ id: '_update2',
155
+ name: 'Update User',
156
+ type: 'update',
157
+ outputEvent: '508 Test Object Updated',
158
+ parameters: [
159
+ {
160
+ id: 'name',
161
+ name: 'Name',
162
+ type: 'string',
163
+ },
164
+ {
165
+ id: 'user',
166
+ type: 'user',
167
+ name: 'User',
168
+ required: false,
169
+ },
170
+ ],
171
+ form: {
172
+ entries: [
173
+ {
174
+ parameterId: 'name',
175
+ type: 'input',
176
+ display: {
177
+ label: 'Name',
178
+ },
179
+ },
180
+ {
181
+ type: 'input',
182
+ parameterId: 'user',
183
+ display: {
184
+ label: 'User',
185
+ },
186
+ },
187
+ ],
188
+ },
189
+ },
190
+ {
191
+ id: '_delete',
192
+ name: 'Delete',
193
+ type: 'delete',
194
+ outputEvent: 'Accessibility Type Deleted',
195
+ },
196
+ {
197
+ id: '_create',
198
+ name: 'Create',
199
+ type: 'create',
200
+ outputEvent: 'Accessibility Type Created',
201
+ },
202
+ ],
203
+ };
81
204
  export const specialtyObject = {
82
205
  id: 'specialty',
83
206
  name: 'Specialty',
@@ -379,3 +502,18 @@ export const npSpecialtyType2 = {
379
502
  name: 'NP License Type',
380
503
  },
381
504
  };
505
+ // Users
506
+ export const users = [
507
+ {
508
+ id: 'user1',
509
+ status: 'Active',
510
+ email: 'user1@systemautomation.com',
511
+ name: 'User 1',
512
+ },
513
+ {
514
+ id: 'user2',
515
+ status: 'Active',
516
+ email: 'user2@systemautomation.com',
517
+ name: 'User 2',
518
+ },
519
+ ];
@@ -2,7 +2,7 @@ import { EvokeForm, ObjectInstance } from '@evoke-platform/context';
2
2
  import React, { ComponentType } from 'react';
3
3
  import { FieldErrors, FieldValues } from 'react-hook-form';
4
4
  import { BaseProps, Document, SimpleEditorProps } from './components/types';
5
- export type FormProps = BaseProps & {
5
+ export type FormRendererProps = BaseProps & {
6
6
  richTextEditor?: ComponentType<SimpleEditorProps>;
7
7
  hideButtons?: boolean;
8
8
  value?: FieldValues;
@@ -19,5 +19,5 @@ export type FormProps = BaseProps & {
19
19
  propertyId?: string;
20
20
  };
21
21
  };
22
- declare function FormRenderer(props: FormProps): React.JSX.Element;
22
+ declare function FormRenderer(props: FormRendererProps): React.JSX.Element;
23
23
  export default FormRenderer;
@@ -1,6 +1,6 @@
1
1
  import React, { ComponentType } from 'react';
2
2
  import { BaseProps, SimpleEditorProps } from './components/types';
3
- export type FormProps = BaseProps & {
3
+ export type FormRendererContainerProps = BaseProps & {
4
4
  formId?: string;
5
5
  instanceId?: string;
6
6
  defaultPages?: Record<string, string>;
@@ -12,6 +12,7 @@ export type FormProps = BaseProps & {
12
12
  };
13
13
  actionId?: string;
14
14
  stickyFooter?: boolean;
15
+ hideButtons?: boolean;
15
16
  objectId: string;
16
17
  richTextEditor?: ComponentType<SimpleEditorProps>;
17
18
  onClose?: () => void;
@@ -21,5 +22,5 @@ export type FormProps = BaseProps & {
21
22
  propertyId?: string;
22
23
  };
23
24
  };
24
- declare function FormRendererContainer(props: FormProps): React.JSX.Element;
25
+ declare function FormRendererContainer(props: FormRendererContainerProps): React.JSX.Element;
25
26
  export default FormRendererContainer;
@@ -9,7 +9,7 @@ import { evalDefaultVals, processValueUpdate } from './components/DefaultValues'
9
9
  import { convertDocToEntries, deleteDocuments, encodePageSlug, formatDataToDoc, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, } from './components/utils';
10
10
  import FormRenderer from './FormRenderer';
11
11
  function FormRendererContainer(props) {
12
- const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, onClose, onSubmit, associatedObject, } = props;
12
+ const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, onClose, onSubmit, associatedObject, hideButtons, } = props;
13
13
  const apiServices = useApiServices();
14
14
  const navigateTo = useNavigate();
15
15
  const { id: appId, defaultPages } = useApp();
@@ -417,7 +417,7 @@ function FormRendererContainer(props) {
417
417
  padding: '0px',
418
418
  border: !isLoading && !onClose ? '1px solid #dbe0e4' : undefined,
419
419
  } },
420
- !isLoading ? (React.createElement(FormRenderer, { onSubmit: onSubmit ?? saveHandler, hideButtons: document && !hasDocumentUpdateAccess, richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, stickyFooter: stickyFooter, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, onCancel: onClose ?? onCancel, associatedObject: associatedObject })) : (React.createElement(Box, { sx: { padding: '20px' } },
420
+ !isLoading ? (React.createElement(FormRenderer, { onSubmit: onSubmit ?? saveHandler, hideButtons: hideButtons ?? (document && !hasDocumentUpdateAccess), richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, stickyFooter: stickyFooter, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, onCancel: onClose ?? onCancel, associatedObject: associatedObject })) : (React.createElement(Box, { sx: { padding: '20px' } },
421
421
  React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
422
422
  React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
423
423
  React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
@@ -41,7 +41,7 @@ const UserProperty = (props) => {
41
41
  }
42
42
  }, [id]);
43
43
  function handleChangeUserProperty(id, value) {
44
- const updatedValue = typeof value.value === 'string' ? { name: value.label, id: value.value } : undefined;
44
+ const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
45
45
  handleChange(id, updatedValue);
46
46
  }
47
47
  return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), PaperComponent: ({ children }) => {
@@ -1,6 +1,5 @@
1
1
  import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
2
2
  import { WarningRounded } from '@mui/icons-material';
3
- import { cloneDeep } from 'lodash';
4
3
  import React, { useEffect, useMemo } from 'react';
5
4
  import { useResponsive } from '../../../../theme';
6
5
  import { useFormContext } from '../../../../theme/hooks';
@@ -70,7 +69,7 @@ export function RecursiveEntryRenderer(props) {
70
69
  return undefined;
71
70
  }
72
71
  if (def?.enum && def.type === 'string') {
73
- const cloned = cloneDeep(def);
72
+ const cloned = structuredClone(def);
74
73
  // single select must be made to be type choices for label and error handling
75
74
  cloned.type = 'choices';
76
75
  return cloned;
@@ -81,7 +81,7 @@ export declare const deleteDocuments: (submittedFields: FieldValues, requestSucc
81
81
  *
82
82
  * Returns the cleaned submission ready for submitting.
83
83
  */
84
- export declare function formatSubmission(submission: FieldValues, apiServices: ApiServices, objectId: string, instanceId?: string, setSnackbarError?: React.Dispatch<React.SetStateAction<{
84
+ export declare function formatSubmission(submission: FieldValues, apiServices?: ApiServices, objectId?: string, instanceId?: string, setSnackbarError?: React.Dispatch<React.SetStateAction<{
85
85
  showAlert: boolean;
86
86
  message?: string;
87
87
  isError: boolean;
@@ -628,7 +628,7 @@ export async function formatSubmission(submission, apiServices, objectId, instan
628
628
  for (const [key, value] of Object.entries(submission)) {
629
629
  if (isArray(value)) {
630
630
  const fileInArray = value.some((item) => item instanceof File);
631
- if (fileInArray && instanceId) {
631
+ if (fileInArray && instanceId && apiServices && objectId) {
632
632
  try {
633
633
  const uploadedDocuments = await uploadDocuments(value, {
634
634
  type: '',
@@ -0,0 +1,173 @@
1
+ import * as matchers from '@testing-library/jest-dom/matchers';
2
+ import { render, screen, waitFor, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { isEqual, set } from 'lodash';
5
+ import { http, HttpResponse } from 'msw';
6
+ import { setupServer } from 'msw/node';
7
+ import React from 'react';
8
+ import { MemoryRouter } from 'react-router-dom';
9
+ import { expect, it } from 'vitest';
10
+ import FormRenderer from '../FormRenderer';
11
+ import { createSpecialtyForm, jsonLogicDisplayTestSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, simpleConditionDisplayTestSpecialtyForm, specialtyObject, specialtyTypeObject, } from './test-data';
12
+ expect.extend(matchers);
13
+ const removePoppers = () => {
14
+ const portalSelectors = ['.MuiAutocomplete-popper'];
15
+ portalSelectors.forEach((selector) => {
16
+ // eslint-disable-next-line testing-library/no-node-access
17
+ document.querySelectorAll(selector).forEach((el) => el.remove());
18
+ });
19
+ };
20
+ describe('Form component', () => {
21
+ let server;
22
+ beforeAll(() => {
23
+ server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
24
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
25
+ if (sanitizedVersion === 'true') {
26
+ return HttpResponse.json(specialtyTypeObject);
27
+ }
28
+ }), http.get('/api/data/objects/license/effective', (req) => {
29
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
30
+ if (sanitizedVersion === 'true') {
31
+ return HttpResponse.json(licenseObject);
32
+ }
33
+ }), http.get('/api/data/objects/specialty/effective', (req) => {
34
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
35
+ if (sanitizedVersion === 'true') {
36
+ return HttpResponse.json(specialtyObject);
37
+ }
38
+ }), http.get('/data/objects/license/effective', () => HttpResponse.json(licenseObject)), http.get('/api/data/objects/license/instances', () => {
39
+ return HttpResponse.json([rnLicense, npLicense]);
40
+ }), http.get('/api/data/objects/specialtyType/instances', (req) => {
41
+ const filter = new URL(req.request.url).searchParams.get('filter');
42
+ if (filter) {
43
+ const whereFilter = JSON.parse(filter).where;
44
+ // The two objects in the array of conditions in the "where" filter represent the potential filters that can be applied when retrieving "specialty" instances.
45
+ // The first object is for the the validation criteria, but it is empty if the "license" field, which is referenced in the validation criteria, hasn't been filled out yet.
46
+ // The second object is for the search criteria which the user enters in the "specialty" field, but it is empty if no search text has been entered.
47
+ if (isEqual(whereFilter, { and: [{}, {}] }))
48
+ return HttpResponse.json([
49
+ rnSpecialtyType1,
50
+ rnSpecialtyType2,
51
+ npSpecialtyType1,
52
+ npSpecialtyType2,
53
+ ]);
54
+ else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'rnLicenseType' }, {}] }))
55
+ return HttpResponse.json([rnSpecialtyType1, rnSpecialtyType2]);
56
+ else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'npLicenseType' }, {}] }))
57
+ return HttpResponse.json([npSpecialtyType1, npSpecialtyType2]);
58
+ }
59
+ }));
60
+ server.listen();
61
+ });
62
+ afterAll(() => {
63
+ server.close();
64
+ });
65
+ afterEach(() => {
66
+ server.resetHandlers();
67
+ removePoppers();
68
+ });
69
+ describe('validation criteria', () => {
70
+ it(`filters related object field with validation criteria that references a related object's nested data`, async () => {
71
+ const user = userEvent.setup();
72
+ server.use(http.get('/data/objects/license/instances/rnLicense', (req) => {
73
+ const expand = new URL(req.request.url).searchParams.get('expand');
74
+ if (expand === 'licenseType.id') {
75
+ return HttpResponse.json(rnLicense);
76
+ }
77
+ }));
78
+ const FormWithState = () => {
79
+ const [formData, setFormData] = React.useState({});
80
+ const handleChange = (id, value) => {
81
+ setFormData((prev) => {
82
+ const newData = { ...prev };
83
+ set(newData, id, value);
84
+ return newData;
85
+ });
86
+ };
87
+ try {
88
+ return React.createElement(FormRenderer, { form: createSpecialtyForm, value: formData, onChange: handleChange });
89
+ }
90
+ catch (err) {
91
+ console.error('Render error:', err);
92
+ return React.createElement("div", null, "Render error");
93
+ }
94
+ };
95
+ render(React.createElement(MemoryRouter, null,
96
+ React.createElement(FormWithState, null)));
97
+ const license = await screen.findByRole('combobox', { name: 'License' });
98
+ // Step 1: Open Specialty Type and verify all options
99
+ await user.click(await screen.findByRole('combobox', { name: 'Specialty Type' }));
100
+ let openAutocomplete = await screen.findByRole('listbox');
101
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
102
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #2' });
103
+ await within(openAutocomplete).findByRole('option', { name: 'NP Specialty Type #1' });
104
+ await within(openAutocomplete).findByRole('option', { name: 'NP Specialty Type #2' });
105
+ await user.keyboard('{Escape}');
106
+ // Step 2: Select a license
107
+ await user.click(license);
108
+ await user.keyboard('{ArrowDown}');
109
+ await user.keyboard('{Enter}');
110
+ // Step 3: Open Specialty Type again and verify filtered options
111
+ await user.click(await screen.findByRole('combobox', { name: 'Specialty Type' }));
112
+ openAutocomplete = await screen.findByRole('listbox');
113
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
114
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #2' });
115
+ await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #1' })).not.toBeInTheDocument());
116
+ await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #2' })).not.toBeInTheDocument());
117
+ await user.keyboard('{Escape}');
118
+ await waitFor(() => {
119
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
120
+ });
121
+ });
122
+ });
123
+ describe('visibility configuration', () => {
124
+ it('shows fields based on instance data using JsonLogic', async () => {
125
+ server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
126
+ render(React.createElement(MemoryRouter, null,
127
+ React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
128
+ id: '123',
129
+ objectId: 'specialty',
130
+ name: 'Test Specialty Object Instance',
131
+ } })));
132
+ // Validate that specialty type dropdown renders
133
+ await screen.findByRole('combobox', { name: 'Specialty Type' });
134
+ });
135
+ it('hides fields based on instance data using JsonLogic', async () => {
136
+ server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
137
+ render(React.createElement(MemoryRouter, null,
138
+ React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
139
+ id: '123',
140
+ objectId: 'specialty',
141
+ name: 'Test Specialty Object Instance -- hidden',
142
+ } })));
143
+ // Validate that license dropdown renders
144
+ await screen.findByRole('combobox', { name: 'License' });
145
+ // Validate that specialty type dropdown does not render
146
+ expect(screen.queryByRole('combobox', { name: 'Specialty Type' })).not.toBeInTheDocument();
147
+ });
148
+ it('shows fields based on instance data using simple conditions', async () => {
149
+ server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
150
+ render(React.createElement(MemoryRouter, null,
151
+ React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
152
+ id: '123',
153
+ objectId: 'specialty',
154
+ name: 'Test Specialty Object Instance',
155
+ } })));
156
+ // Validate that specialty type dropdown renders
157
+ await screen.findByRole('combobox', { name: 'Specialty Type' });
158
+ });
159
+ it('hides fields based on instance data using simple conditions', async () => {
160
+ server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
161
+ render(React.createElement(MemoryRouter, null,
162
+ React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
163
+ id: '123',
164
+ objectId: 'specialty',
165
+ name: 'Test Specialty Object Instance -- hidden',
166
+ } })));
167
+ // Validate that license dropdown renders
168
+ await screen.findByRole('combobox', { name: 'License' });
169
+ // Validate that specialty type dropdown does not render
170
+ expect(screen.queryByRole('combobox', { name: 'Specialty Type' })).not.toBeInTheDocument();
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,96 @@
1
+ import * as matchers from '@testing-library/jest-dom/matchers';
2
+ import { render, screen, waitFor, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { isEqual } from 'lodash';
5
+ import { http, HttpResponse } from 'msw';
6
+ import { setupServer } from 'msw/node';
7
+ import React from 'react';
8
+ import { MemoryRouter } from 'react-router-dom';
9
+ import { expect, it } from 'vitest';
10
+ import FormRendererContainer from '../FormRendererContainer';
11
+ import { createSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
12
+ expect.extend(matchers);
13
+ const removePoppers = () => {
14
+ const portalSelectors = ['.MuiAutocomplete-popper'];
15
+ portalSelectors.forEach((selector) => {
16
+ // eslint-disable-next-line testing-library/no-node-access
17
+ document.querySelectorAll(selector).forEach((el) => el.remove());
18
+ });
19
+ };
20
+ describe('Form component', () => {
21
+ let server;
22
+ beforeAll(() => {
23
+ server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
24
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
25
+ if (sanitizedVersion === 'true') {
26
+ return HttpResponse.json(specialtyTypeObject);
27
+ }
28
+ }), http.get('/api/data/objects/license/effective', (req) => {
29
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
30
+ if (sanitizedVersion === 'true') {
31
+ return HttpResponse.json(licenseObject);
32
+ }
33
+ }), http.get('/api/data/objects/specialty/effective', (req) => {
34
+ const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
35
+ if (sanitizedVersion === 'true') {
36
+ return HttpResponse.json(specialtyObject);
37
+ }
38
+ }), http.get('/data/objects/license/effective', () => HttpResponse.json(licenseObject)), http.get('/api/data/objects/license/instances', () => {
39
+ return HttpResponse.json([rnLicense, npLicense]);
40
+ }), http.get('/api/data/objects/specialtyType/instances', (req) => {
41
+ const filter = new URL(req.request.url).searchParams.get('filter');
42
+ if (filter) {
43
+ const whereFilter = JSON.parse(filter).where;
44
+ // The two objects in the array of conditions in the "where" filter represent the potential filters that can be applied when retrieving "specialty" instances.
45
+ // The first object is for the the validation criteria, but it is empty if the "license" field, which is referenced in the validation criteria, hasn't been filled out yet.
46
+ // The second object is for the search criteria which the user enters in the "specialty" field, but it is empty if no search text has been entered.
47
+ if (isEqual(whereFilter, { and: [{}, {}] }))
48
+ return HttpResponse.json([
49
+ rnSpecialtyType1,
50
+ rnSpecialtyType2,
51
+ npSpecialtyType1,
52
+ npSpecialtyType2,
53
+ ]);
54
+ else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'rnLicenseType' }, {}] }))
55
+ return HttpResponse.json([rnSpecialtyType1, rnSpecialtyType2]);
56
+ else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'npLicenseType' }, {}] }))
57
+ return HttpResponse.json([npSpecialtyType1, npSpecialtyType2]);
58
+ }
59
+ }));
60
+ server.listen();
61
+ });
62
+ afterAll(() => {
63
+ server.close();
64
+ });
65
+ afterEach(() => {
66
+ server.resetHandlers();
67
+ removePoppers();
68
+ });
69
+ describe('validation criteria', () => {
70
+ it(`filters related object field with validation criteria that references a defaulted related object's nested data`, async () => {
71
+ const user = userEvent.setup();
72
+ server.use(http.get('/api/data/objects/license/instances/rnLicense', () => {
73
+ return HttpResponse.json(rnLicense);
74
+ }), http.get('api/data/forms/specialtyForm', () => {
75
+ return HttpResponse.json(createSpecialtyForm);
76
+ }));
77
+ render(React.createElement(MemoryRouter, null,
78
+ React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_create', associatedObject: { propertyId: 'license', instanceId: 'rnLicense' } })));
79
+ // Validate that the license field is hidden
80
+ await waitFor(() => expect(screen.queryByRole('combobox', { name: 'License' })).not.toBeInTheDocument());
81
+ // Validate that specialty type dropdown is only rendering specialty types that are associated with the selected license.
82
+ const specialtyType = await screen.findByRole('combobox', { name: 'Specialty Type' });
83
+ await new Promise((r) => setTimeout(r, 5000));
84
+ await user.click(specialtyType);
85
+ const openAutocomplete = await screen.findByRole('listbox');
86
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
87
+ await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #2' });
88
+ await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #1' })).not.toBeInTheDocument());
89
+ await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #2' })).not.toBeInTheDocument());
90
+ await user.keyboard('{Escape}');
91
+ await waitFor(() => {
92
+ expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
93
+ });
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,16 @@
1
+ import { EvokeForm, Obj, ObjectInstance } from '@evoke-platform/context';
2
+ export declare const createSpecialtyForm: EvokeForm;
3
+ export declare const simpleConditionDisplayTestSpecialtyForm: EvokeForm;
4
+ export declare const jsonLogicDisplayTestSpecialtyForm: EvokeForm;
5
+ export declare const licenseObject: Obj;
6
+ export declare const licenseTypeObject: Obj;
7
+ export declare const specialtyObject: Obj;
8
+ export declare const specialtyTypeObject: Obj;
9
+ export declare const rnLicense: ObjectInstance;
10
+ export declare const npLicense: ObjectInstance;
11
+ export declare const rnLicenseType: ObjectInstance;
12
+ export declare const npLicesneType: ObjectInstance;
13
+ export declare const rnSpecialtyType1: ObjectInstance;
14
+ export declare const rnSpecialtyType2: ObjectInstance;
15
+ export declare const npSpecialtyType1: ObjectInstance;
16
+ export declare const npSpecialtyType2: ObjectInstance;