@evoke-platform/ui-components 1.4.0-testing.1 → 1.4.0-testing.11
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/dist/published/components/core/Alert/Alert.js +1 -1
- package/dist/published/components/core/Autocomplete/Autocomplete.js +3 -3
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +2 -18
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +2 -2
- package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +25 -17
- package/dist/published/components/custom/CriteriaBuilder/index.d.ts +2 -1
- package/dist/published/components/custom/CriteriaBuilder/index.js +2 -1
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +13 -0
- package/dist/published/components/custom/CriteriaBuilder/utils.js +58 -1
- package/dist/published/components/custom/Form/Common/Form.js +37 -26
- package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +2 -1
- package/dist/published/components/custom/Form/FormComponents/CriteriaComponent/Criteria.js +52 -1
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.d.ts +2 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +75 -26
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +3 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +5 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +4 -6
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +4 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +31 -13
- package/dist/published/components/custom/Form/tests/Form.test.d.ts +1 -0
- package/dist/published/components/custom/Form/tests/Form.test.js +158 -0
- package/dist/published/components/custom/Form/tests/test-data.d.ts +13 -0
- package/dist/published/components/custom/Form/tests/test-data.js +381 -0
- package/dist/published/components/custom/Form/utils.d.ts +10 -4
- package/dist/published/components/custom/Form/utils.js +213 -90
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/HistoryLog/DisplayedProperty.d.ts +2 -1
- package/dist/published/components/custom/HistoryLog/DisplayedProperty.js +5 -2
- package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
- package/dist/published/components/custom/HistoryLog/HistoryData.js +9 -3
- package/dist/published/components/custom/HistoryLog/index.js +24 -2
- package/dist/published/components/custom/index.d.ts +1 -1
- package/dist/published/components/custom/index.js +1 -1
- package/dist/published/index.d.ts +1 -1
- package/dist/published/index.js +1 -1
- package/package.json +3 -4
package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
import { ReactComponent } from '@formio/react';
|
2
|
-
import
|
3
|
-
import { cloneDeep, isEmpty, pick } from 'lodash';
|
2
|
+
import { cloneDeep, isEmpty, isNil, isObject, pick } from 'lodash';
|
4
3
|
import React from 'react';
|
5
4
|
import ReactDOM from 'react-dom';
|
6
5
|
import { FormComponentWrapper } from '../../Common';
|
7
|
-
import {
|
6
|
+
import { getCriteriaInputs, getPrefixedUrl, populateInstanceWithNestedData, transformToWhere, updateCriteriaInputs, } from '../../utils';
|
8
7
|
import { ObjectPropertyInput } from './ObjectPropertyInput';
|
9
8
|
export class ObjectComponent extends ReactComponent {
|
10
9
|
constructor(component, options, data) {
|
@@ -17,9 +16,9 @@ export class ObjectComponent extends ReactComponent {
|
|
17
16
|
delete this.errorDetails['api-error'];
|
18
17
|
const updatedValue = pick(value, 'id', 'name');
|
19
18
|
// set the value on the form instance at this.root.data
|
20
|
-
this.setValue(updatedValue
|
19
|
+
this.setValue(!isNil(updatedValue) && !isEmpty(updatedValue) ? updatedValue : '');
|
21
20
|
// update the value in the component instance
|
22
|
-
this.updateValue(updatedValue
|
21
|
+
this.updateValue(!isNil(updatedValue) ? updatedValue : {}, { modified: true });
|
23
22
|
this.handleValidation();
|
24
23
|
this.emit('changed-' + this.component.key, value);
|
25
24
|
this.attach(this.element);
|
@@ -33,25 +32,44 @@ export class ObjectComponent extends ReactComponent {
|
|
33
32
|
this.handleChangeObjectProperty = this.handleChangeObjectProperty.bind(this);
|
34
33
|
}
|
35
34
|
init() {
|
36
|
-
const data = dot.dot(this.root._data);
|
37
35
|
if (this.criteria) {
|
38
|
-
const inputProps =
|
36
|
+
const inputProps = getCriteriaInputs(this.criteria);
|
37
|
+
this.updatedCriteria = updateCriteriaInputs(this.criteria, this.root._data, this.component.user);
|
39
38
|
for (const inputProp of inputProps) {
|
40
|
-
// Parse data to update criteria when form is loaded.
|
41
|
-
updateCriteriaInputs(this.updatedCriteria, inputProp, data[inputProp], true);
|
42
39
|
// Parse data to update criteria when form field is updated
|
43
40
|
// Need to parse all fields again.
|
44
41
|
const compKeyFragments = inputProp.split('.');
|
45
42
|
let compKey = compKeyFragments[0];
|
46
|
-
|
43
|
+
const property = this.component.properties.find((c) => c.id === compKey);
|
44
|
+
if (property?.type === 'address' &&
|
45
|
+
['line1', 'line2', 'city', 'state', 'zipCode'].includes(compKeyFragments[1])) {
|
47
46
|
compKey = inputProp;
|
48
47
|
}
|
49
|
-
this.on(`changed-${compKey}`, () => {
|
50
|
-
const data =
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
this.on(`changed-${compKey}`, async (value) => {
|
49
|
+
const data = this.root._data;
|
50
|
+
if (property?.type === 'object' && isObject(value) && 'id' in value && 'objectId' in value) {
|
51
|
+
const paths = this.component.allCriteriaInputs
|
52
|
+
.filter((input) => input.split('.')[0] === compKey)
|
53
|
+
.map((p) => p.split('.').slice(1).join('.'));
|
54
|
+
let instance = value;
|
55
|
+
if (!isEmpty(paths)) {
|
56
|
+
instance = await populateInstanceWithNestedData(value['id'], value['objectId'], paths, this.component.apiServices);
|
57
|
+
}
|
58
|
+
data[compKey] = instance;
|
54
59
|
}
|
60
|
+
else {
|
61
|
+
if (compKey.includes('.')) {
|
62
|
+
const keyFragments = compKey.split('.');
|
63
|
+
if (!data[keyFragments[0]]) {
|
64
|
+
data[keyFragments[0]] = {};
|
65
|
+
}
|
66
|
+
data[keyFragments[0]][keyFragments[1]] = value;
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
data[compKey] = value;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
this.updatedCriteria = updateCriteriaInputs(this.criteria ?? {}, data, this.component.user);
|
55
73
|
if (this.visible) {
|
56
74
|
this.attachReact(this.element);
|
57
75
|
}
|
@@ -59,25 +77,43 @@ export class ObjectComponent extends ReactComponent {
|
|
59
77
|
}
|
60
78
|
}
|
61
79
|
if (this.defaultValueCriteria) {
|
62
|
-
const inputProps =
|
63
|
-
updateCriteriaInputs(this.
|
80
|
+
const inputProps = getCriteriaInputs(this.defaultValueCriteria);
|
81
|
+
this.updatedDefaultValueCriteria = updateCriteriaInputs(this.defaultValueCriteria, this.root._data, this.component.user);
|
64
82
|
for (const inputProp of inputProps) {
|
65
|
-
// Parse data to update criteria when form is loaded.
|
66
|
-
updateCriteriaInputs(this.updatedDefaultValueCriteria, inputProp, data[inputProp], true);
|
67
83
|
// Parse data to update criteria when form field is updated
|
68
84
|
// Need to parse all fields again.
|
69
85
|
const compKeyFragments = inputProp.split('.');
|
70
86
|
let compKey = compKeyFragments[0];
|
71
|
-
|
87
|
+
const property = this.component.properties.find((c) => c.id === compKey);
|
88
|
+
if (property?.type === 'address' &&
|
89
|
+
['line1', 'line2', 'city', 'state', 'zipCode'].includes(compKeyFragments[1])) {
|
72
90
|
compKey = inputProp;
|
73
91
|
}
|
74
|
-
this.on(`changed-${compKey}`, () => {
|
75
|
-
const data =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
this.on(`changed-${compKey}`, async (value) => {
|
93
|
+
const data = this.root._data;
|
94
|
+
if (property?.type === 'object' && isObject(value) && 'id' in value && 'objectId' in value) {
|
95
|
+
const paths = this.component.allCriteriaInputs
|
96
|
+
.filter((input) => input.split('.')[0] === compKey)
|
97
|
+
.map((p) => p.split('.').slice(1).join('.'));
|
98
|
+
let instance = value;
|
99
|
+
if (!isEmpty(paths)) {
|
100
|
+
instance = await populateInstanceWithNestedData(value['id'], value['objectId'], paths, this.component.apiServices);
|
101
|
+
}
|
102
|
+
data[compKey] = instance;
|
103
|
+
}
|
104
|
+
else {
|
105
|
+
if (compKey.includes('.')) {
|
106
|
+
const keyFragments = compKey.split('.');
|
107
|
+
if (!data[keyFragments[0]]) {
|
108
|
+
data[keyFragments[0]] = {};
|
109
|
+
}
|
110
|
+
data[keyFragments[0]][keyFragments[1]] = value;
|
111
|
+
}
|
112
|
+
else {
|
113
|
+
data[compKey] = value;
|
114
|
+
}
|
80
115
|
}
|
116
|
+
this.updatedDefaultValueCriteria = updateCriteriaInputs(this.defaultValueCriteria ?? {}, data, this.component.user);
|
81
117
|
if (this.visible) {
|
82
118
|
this.attachReact(this.element);
|
83
119
|
}
|
@@ -124,6 +160,19 @@ export class ObjectComponent extends ReactComponent {
|
|
124
160
|
this.attach(this.element);
|
125
161
|
this.attachReact(this.element);
|
126
162
|
});
|
163
|
+
if (this.component.defaultValue) {
|
164
|
+
this.expandInstance();
|
165
|
+
}
|
166
|
+
}
|
167
|
+
async expandInstance() {
|
168
|
+
const { property, apiServices, defaultValue } = this.component;
|
169
|
+
const paths = this.component.allCriteriaInputs
|
170
|
+
.filter((input) => input.split('.')[0] === this.component.key)
|
171
|
+
.map((p) => p.split('.').slice(1).join('.'));
|
172
|
+
if (!isEmpty(paths)) {
|
173
|
+
const instance = await populateInstanceWithNestedData(defaultValue.id, property.objectId, paths, apiServices);
|
174
|
+
this.handleChangeObjectProperty(property.id, instance);
|
175
|
+
}
|
127
176
|
}
|
128
177
|
clearErrors() {
|
129
178
|
this.errorDetails = {};
|
package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js
CHANGED
@@ -227,7 +227,7 @@ export const ObjectPropertyInput = (props) => {
|
|
227
227
|
return option.value === value?.value;
|
228
228
|
}, options: options.map((o) => ({ label: o.name, value: o.id })), getOptionLabel: (option) => {
|
229
229
|
return typeof option === 'string'
|
230
|
-
? options.find((o) => o.id === option)?.name ?? ''
|
230
|
+
? (options.find((o) => o.id === option)?.name ?? '')
|
231
231
|
: option.label;
|
232
232
|
}, onKeyDownCapture: (e) => {
|
233
233
|
if (instance?.[property.id]?.id || selectedInstance?.id) {
|
@@ -292,7 +292,8 @@ export const ObjectPropertyInput = (props) => {
|
|
292
292
|
}
|
293
293
|
: {}),
|
294
294
|
} })), readOnly: !loadingOptions && !canUpdateProperty, error: error, sortBy: "NONE" }))) : (React.createElement(Box, { sx: {
|
295
|
-
padding: (instance?.[property.id]?.name ??
|
295
|
+
padding: (instance?.[property.id]?.name ??
|
296
|
+
selectedInstance?.name)
|
296
297
|
? '16.5px 14px'
|
297
298
|
: '10.5px 0',
|
298
299
|
} },
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Action, ApiServices, Obj,
|
1
|
+
import { Action, ApiServices, Obj, UserAccount } from '@evoke-platform/context';
|
2
2
|
import React from 'react';
|
3
3
|
import { Address, ObjectPropertyInputProps } from '../../types';
|
4
4
|
export type ActionDialogProps = {
|
@@ -13,9 +13,12 @@ export type ActionDialogProps = {
|
|
13
13
|
object: Obj;
|
14
14
|
objectInputCommonProps: ObjectPropertyInputProps;
|
15
15
|
instanceId?: string;
|
16
|
-
relatedProperty?: Property;
|
17
16
|
apiServices: ApiServices;
|
18
17
|
queryAddresses?: (query: string) => Promise<Address[]>;
|
19
18
|
user?: UserAccount;
|
19
|
+
associatedObject?: {
|
20
|
+
instanceId: string;
|
21
|
+
propertyId: string;
|
22
|
+
};
|
20
23
|
};
|
21
24
|
export declare const ActionDialog: (props: ActionDialogProps) => React.JSX.Element;
|
@@ -36,7 +36,7 @@ const styles = {
|
|
36
36
|
},
|
37
37
|
};
|
38
38
|
export const ActionDialog = (props) => {
|
39
|
-
const { open, onClose, action, instanceInput, handleSubmit, apiServices, object, instanceId,
|
39
|
+
const { open, onClose, action, instanceInput, handleSubmit, apiServices, object, instanceId, objectInputCommonProps, queryAddresses, associatedObject, user, } = props;
|
40
40
|
const [updatedObject, setUpdatedObject] = useState();
|
41
41
|
const [hasAccess, setHasAccess] = useState(false);
|
42
42
|
const [loading, setLoading] = useState(false);
|
@@ -57,18 +57,16 @@ export const ActionDialog = (props) => {
|
|
57
57
|
}, [object, instanceId]);
|
58
58
|
useEffect(() => {
|
59
59
|
const input = (action.form?.entries && action.parameters) || action?.inputProperties
|
60
|
-
?
|
60
|
+
? action.form?.entries && action.parameters
|
61
61
|
? convertFormToComponents(action.form.entries, action.parameters, object)
|
62
|
-
: action?.inputProperties
|
62
|
+
: action?.inputProperties
|
63
63
|
: undefined;
|
64
64
|
const updatedAction = {
|
65
65
|
...action,
|
66
66
|
form: input ? { entries: convertComponentsToForm(input) } : undefined,
|
67
67
|
};
|
68
|
-
const properties = object.properties?.filter((prop) => prop.id !== relatedProperty?.relatedPropertyId);
|
69
68
|
setUpdatedObject({
|
70
69
|
...object,
|
71
|
-
properties,
|
72
70
|
actions: concat(object.actions?.filter((a) => a.id !== action.id) ?? [], [updatedAction]),
|
73
71
|
});
|
74
72
|
}, [object]);
|
@@ -78,7 +76,7 @@ export const ActionDialog = (props) => {
|
|
78
76
|
React.createElement(IconButton, { sx: styles.closeIcon, onClick: onClose },
|
79
77
|
React.createElement(Close, { fontSize: "small" })),
|
80
78
|
action && hasAccess && !loading ? action?.name : ''),
|
81
|
-
React.createElement(DialogContent, null, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } }, (updatedObject || isDeleteAction) && (React.createElement(Form, { actionId: action.id, actionType: action.type, apiServices: objectInputCommonProps.apiServices, object: !isDeleteAction ? updatedObject : object, instance: instanceInput, onSave: async (data, setSubmitting) => handleSubmit(action.type, data, instanceId, setSubmitting), objectInputCommonProps: objectInputCommonProps, closeModal: onClose, queryAddresses: queryAddresses, user: user, submitButtonLabel: isDeleteAction ? 'Delete' : undefined })))) : (React.createElement(React.Fragment, null, loading ? (React.createElement(React.Fragment, null,
|
79
|
+
React.createElement(DialogContent, null, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } }, (updatedObject || isDeleteAction) && (React.createElement(Form, { actionId: action.id, actionType: action.type, apiServices: objectInputCommonProps.apiServices, object: !isDeleteAction ? updatedObject : object, instance: instanceInput, onSave: async (data, setSubmitting) => handleSubmit(action.type, data, instanceId, setSubmitting), objectInputCommonProps: objectInputCommonProps, closeModal: onClose, queryAddresses: queryAddresses, user: user, submitButtonLabel: isDeleteAction ? 'Delete' : undefined, associatedObject: associatedObject })))) : (React.createElement(React.Fragment, null, loading ? (React.createElement(React.Fragment, null,
|
82
80
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
83
81
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
84
82
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }))) : (React.createElement(ErrorComponent, { code: 'AccessDenied', message: 'You do not have permission to perform this action.', styles: { boxShadow: 'none' } })))))));
|
@@ -402,10 +402,12 @@ const RepeatableField = (props) => {
|
|
402
402
|
React.createElement(Tooltip, { title: "Delete" },
|
403
403
|
React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))),
|
404
404
|
hasCreateAction && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow }, "Add"))),
|
405
|
-
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, apiServices: apiServices, onClose: () => setOpenDialog(false), instanceInput: dialogType === 'update' ? relatedInstances.find((i) => i.id === selectedRow) ?? {} : {}, handleSubmit: save,
|
405
|
+
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, apiServices: apiServices, onClose: () => setOpenDialog(false), instanceInput: dialogType === 'update' ? (relatedInstances.find((i) => i.id === selectedRow) ?? {}) : {}, handleSubmit: save,
|
406
406
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
407
407
|
objectInputCommonProps: { apiServices }, action: relatedObject?.actions?.find((a) => a.id ===
|
408
|
-
(dialogType === 'create' ? '_create' : dialogType === 'update' ? '_update' : '_delete')), instanceId: selectedRow,
|
408
|
+
(dialogType === 'create' ? '_create' : dialogType === 'update' ? '_update' : '_delete')), instanceId: selectedRow, queryAddresses: queryAddresses, user: user, associatedObject: instance.id && property.relatedPropertyId
|
409
|
+
? { instanceId: instance.id, propertyId: property.relatedPropertyId }
|
410
|
+
: undefined })),
|
409
411
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })));
|
410
412
|
};
|
411
413
|
export default RepeatableField;
|
@@ -1,11 +1,10 @@
|
|
1
1
|
import { ApiBaseUrlProvider, NotificationProvider, } from '@evoke-platform/context';
|
2
2
|
import { ReactComponent } from '@formio/react';
|
3
|
-
import
|
4
|
-
import { cloneDeep } from 'lodash';
|
3
|
+
import { cloneDeep, isEmpty, isObject } from 'lodash';
|
5
4
|
import React from 'react';
|
6
5
|
import ReactDOM from 'react-dom';
|
7
6
|
import { FormComponentWrapper } from '../../Common';
|
8
|
-
import {
|
7
|
+
import { getCriteriaInputs, populateInstanceWithNestedData, updateCriteriaInputs } from '../../utils';
|
9
8
|
import { DropdownRepeatableField } from './ManyToMany/DropdownRepeatableField';
|
10
9
|
import RepeatableField from './RepeatableField';
|
11
10
|
const apiBaseUrl = process.env.REACT_APP_API_ROOT || `${window.location.origin}/api`;
|
@@ -23,24 +22,43 @@ export class RepeatableFieldComponent extends ReactComponent {
|
|
23
22
|
}
|
24
23
|
init() {
|
25
24
|
if (this.criteria) {
|
26
|
-
const inputProps =
|
27
|
-
|
25
|
+
const inputProps = getCriteriaInputs(this.criteria);
|
26
|
+
this.updatedCriteria = updateCriteriaInputs(this.updatedCriteria, this.root._data, this.component.user);
|
28
27
|
for (const inputProp of inputProps) {
|
29
|
-
// Parse data to update criteria when form is loaded.
|
30
|
-
updateCriteriaInputs(this.updatedCriteria, inputProp, data[inputProp], true);
|
31
28
|
// Parse data to update criteria when form field is updated
|
32
29
|
// Need to parse all fields again.
|
33
30
|
const compKeyFragments = inputProp.split('.');
|
34
31
|
let compKey = compKeyFragments[0];
|
35
|
-
|
32
|
+
const property = this.component.properties.find((c) => c.id === compKey);
|
33
|
+
if (property?.type === 'address' &&
|
34
|
+
['line1', 'line2', 'city', 'state', 'zipCode'].includes(compKeyFragments[1])) {
|
36
35
|
compKey = inputProp;
|
37
36
|
}
|
38
|
-
this.on(`changed-${compKey}`, () => {
|
39
|
-
const data =
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
this.on(`changed-${compKey}`, async (value) => {
|
38
|
+
const data = this.root._data;
|
39
|
+
if (property?.type === 'object' && isObject(value) && 'id' in value && 'objectId' in value) {
|
40
|
+
const paths = this.component.allCriteriaInputs
|
41
|
+
.filter((input) => input.split('.')[0] === compKey)
|
42
|
+
.map((p) => p.split('.').slice(1).join('.'));
|
43
|
+
let instance = value;
|
44
|
+
if (!isEmpty(paths)) {
|
45
|
+
instance = await populateInstanceWithNestedData(value['id'], value['objectId'], paths, this.component.apiServices);
|
46
|
+
}
|
47
|
+
data[compKey] = instance;
|
43
48
|
}
|
49
|
+
else {
|
50
|
+
if (compKey.includes('.')) {
|
51
|
+
const keyFragments = compKey.split('.');
|
52
|
+
if (!data[keyFragments[0]]) {
|
53
|
+
data[keyFragments[0]] = {};
|
54
|
+
}
|
55
|
+
data[keyFragments[0]][keyFragments[1]] = value;
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
data[compKey] = value;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
this.updatedCriteria = updateCriteriaInputs(this.criteria ?? {}, data, this.component.user);
|
44
62
|
this.attachReact(this.element);
|
45
63
|
});
|
46
64
|
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,158 @@
|
|
1
|
+
import { ApiServices } from '@evoke-platform/context';
|
2
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
3
|
+
import userEvent from '@testing-library/user-event';
|
4
|
+
import axios from 'axios';
|
5
|
+
import { isEqual } from 'lodash';
|
6
|
+
import { http, HttpResponse } from 'msw';
|
7
|
+
import { setupServer } from 'msw/node';
|
8
|
+
import React from 'react';
|
9
|
+
import { it } from 'vitest';
|
10
|
+
import Form from '../Common/Form';
|
11
|
+
import { licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
|
12
|
+
const removePoppers = () => {
|
13
|
+
const portalSelectors = ['.MuiAutocomplete-popper'];
|
14
|
+
portalSelectors.forEach((selector) => {
|
15
|
+
// eslint-disable-next-line testing-library/no-node-access
|
16
|
+
document.querySelectorAll(selector).forEach((el) => el.remove());
|
17
|
+
});
|
18
|
+
};
|
19
|
+
describe('Form component', () => {
|
20
|
+
let server;
|
21
|
+
let apiServices;
|
22
|
+
beforeAll(() => {
|
23
|
+
server = setupServer(http.get('/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/data/objects/license/effective', () => HttpResponse.json(licenseObject)), http.get('/data/objects/license/instances', () => {
|
24
|
+
return HttpResponse.json([rnLicense, npLicense]);
|
25
|
+
}), http.get('/data/objects/specialtyType/instances', (req) => {
|
26
|
+
const filter = new URL(req.request.url).searchParams.get('filter');
|
27
|
+
if (filter) {
|
28
|
+
const whereFilter = JSON.parse(filter).where;
|
29
|
+
// The two objects in the array of conditions in the "where" filter represent the potential filters that can be applied when retrieving "specialty" instances.
|
30
|
+
// 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.
|
31
|
+
// 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.
|
32
|
+
if (isEqual(whereFilter, { and: [{}, {}] }))
|
33
|
+
return HttpResponse.json([
|
34
|
+
rnSpecialtyType1,
|
35
|
+
rnSpecialtyType2,
|
36
|
+
npSpecialtyType1,
|
37
|
+
npSpecialtyType2,
|
38
|
+
]);
|
39
|
+
else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'rnLicenseType' }, {}] }))
|
40
|
+
return HttpResponse.json([rnSpecialtyType1, rnSpecialtyType2]);
|
41
|
+
else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'npLicenseType' }, {}] }))
|
42
|
+
return HttpResponse.json([npSpecialtyType1, npSpecialtyType2]);
|
43
|
+
}
|
44
|
+
}));
|
45
|
+
server.listen();
|
46
|
+
});
|
47
|
+
beforeEach(() => {
|
48
|
+
apiServices = new ApiServices(axios.create());
|
49
|
+
});
|
50
|
+
afterAll(() => {
|
51
|
+
server.close();
|
52
|
+
});
|
53
|
+
afterEach(() => {
|
54
|
+
server.resetHandlers();
|
55
|
+
removePoppers();
|
56
|
+
});
|
57
|
+
describe('validation criteria', () => {
|
58
|
+
it(`filters related object field with validation criteria that references a related object's nested data`, async () => {
|
59
|
+
const user = userEvent.setup();
|
60
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', (req) => {
|
61
|
+
const expand = new URL(req.request.url).searchParams.get('expand');
|
62
|
+
if (expand === 'licenseType.id') {
|
63
|
+
return HttpResponse.json(rnLicense);
|
64
|
+
}
|
65
|
+
}));
|
66
|
+
render(React.createElement(Form, { actionId: '_create', actionType: 'create', object: specialtyObject, apiServices: apiServices }));
|
67
|
+
const license = await screen.findByRole('combobox', { name: 'License' });
|
68
|
+
// Validate that specialty type dropdown is rendering all options
|
69
|
+
let specialtyType = await screen.findByRole('combobox', { name: 'Specialty Type' });
|
70
|
+
await user.click(specialtyType);
|
71
|
+
let openAutocomplete = await screen.findByRole('listbox');
|
72
|
+
await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
|
73
|
+
await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #2' });
|
74
|
+
await within(openAutocomplete).findByRole('option', { name: 'NP Specialty Type #1' });
|
75
|
+
await within(openAutocomplete).findByRole('option', { name: 'NP Specialty Type #2' });
|
76
|
+
// Close the specialty type dropdown
|
77
|
+
removePoppers();
|
78
|
+
// Select a license from the dropdown
|
79
|
+
await user.click(license);
|
80
|
+
const rnLicenseOption = await screen.findByRole('option', { name: 'RN License' });
|
81
|
+
await user.click(rnLicenseOption);
|
82
|
+
// Validate that specialty type dropdown is only rendering specialty types that are associated with the selected license.
|
83
|
+
specialtyType = await screen.findByRole('combobox', { name: 'Specialty Type' });
|
84
|
+
await user.click(specialtyType);
|
85
|
+
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' })).to.be.null);
|
89
|
+
await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #2' })).to.be.null);
|
90
|
+
});
|
91
|
+
it(`filters related object field with validation criteria that references a defaulted related object's nested data`, async () => {
|
92
|
+
const user = userEvent.setup();
|
93
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', (req) => {
|
94
|
+
const expand = new URL(req.request.url).searchParams.get('expand');
|
95
|
+
if (expand === 'licenseType.id') {
|
96
|
+
return HttpResponse.json(rnLicense);
|
97
|
+
}
|
98
|
+
}));
|
99
|
+
render(React.createElement(Form, { actionId: '_create', actionType: 'create', object: specialtyObject, apiServices: apiServices, associatedObject: { propertyId: 'license', instanceId: 'rnLicense' } }));
|
100
|
+
// Validate that the license field is hidden
|
101
|
+
await waitFor(() => expect(screen.queryByRole('combobox', { name: 'License' })).to.be.null);
|
102
|
+
// Validate that specialty type dropdown is only rendering specialty types that are associated with the selected license.
|
103
|
+
const specialtyType = await screen.findByRole('combobox', { name: 'Specialty Type' });
|
104
|
+
await user.click(specialtyType);
|
105
|
+
const openAutocomplete = await screen.findByRole('listbox');
|
106
|
+
await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
|
107
|
+
await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #2' });
|
108
|
+
await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #1' })).to.be.null);
|
109
|
+
await waitFor(() => expect(within(openAutocomplete).queryByRole('option', { name: 'NP Specialty Type #2' })).to.be.null);
|
110
|
+
});
|
111
|
+
});
|
112
|
+
describe('visibility configuration', () => {
|
113
|
+
it('shows fields based on instance data using JsonLogic', async () => {
|
114
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
|
115
|
+
render(React.createElement(Form, { actionId: 'jsonLogicDisplayTest', actionType: 'update', object: specialtyObject, apiServices: apiServices, instance: {
|
116
|
+
id: '123',
|
117
|
+
objectId: 'specialty',
|
118
|
+
name: 'Test Specialty Object Instance',
|
119
|
+
} }));
|
120
|
+
// Validate that specialty type dropdown renders
|
121
|
+
await screen.findByRole('combobox', { name: 'Specialty Type' });
|
122
|
+
});
|
123
|
+
it('hides fields based on instance data using JsonLogic', async () => {
|
124
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
|
125
|
+
render(React.createElement(Form, { actionId: 'jsonLogicDisplayTest', actionType: 'update', object: specialtyObject, apiServices: apiServices, instance: {
|
126
|
+
id: '123',
|
127
|
+
objectId: 'specialty',
|
128
|
+
name: 'Test Specialty Object Instance -- hidden',
|
129
|
+
} }));
|
130
|
+
// Validate that license dropdown renders
|
131
|
+
await screen.findByRole('combobox', { name: 'License' });
|
132
|
+
// Validate that specialty type dropdown does not render
|
133
|
+
expect(screen.queryByRole('combobox', { name: 'Specialty Type' })).to.be.null;
|
134
|
+
});
|
135
|
+
it('shows fields based on instance data using simple conditions', async () => {
|
136
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
|
137
|
+
render(React.createElement(Form, { actionId: 'simpleConditionDisplayTest', actionType: 'update', object: specialtyObject, apiServices: apiServices, instance: {
|
138
|
+
id: '123',
|
139
|
+
objectId: 'specialty',
|
140
|
+
name: 'Test Specialty Object Instance',
|
141
|
+
} }));
|
142
|
+
// Validate that specialty type dropdown renders
|
143
|
+
await screen.findByRole('combobox', { name: 'Specialty Type' });
|
144
|
+
});
|
145
|
+
it('hides fields based on instance data using simple conditions', async () => {
|
146
|
+
server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
|
147
|
+
render(React.createElement(Form, { actionId: 'simpleConditionDisplayTest', actionType: 'update', object: specialtyObject, apiServices: apiServices, instance: {
|
148
|
+
id: '123',
|
149
|
+
objectId: 'specialty',
|
150
|
+
name: 'Test Specialty Object Instance -- hidden',
|
151
|
+
} }));
|
152
|
+
// Validate that license dropdown renders
|
153
|
+
await screen.findByRole('combobox', { name: 'License' });
|
154
|
+
// Validate that specialty type dropdown does not render
|
155
|
+
expect(screen.queryByRole('combobox', { name: 'Specialty Type' })).to.be.null;
|
156
|
+
});
|
157
|
+
});
|
158
|
+
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Obj, ObjectInstance } from '@evoke-platform/context';
|
2
|
+
export declare const licenseObject: Obj;
|
3
|
+
export declare const licenseTypeObject: Obj;
|
4
|
+
export declare const specialtyObject: Obj;
|
5
|
+
export declare const specialtyTypeObject: Obj;
|
6
|
+
export declare const rnLicense: ObjectInstance;
|
7
|
+
export declare const npLicense: ObjectInstance;
|
8
|
+
export declare const rnLicenseType: ObjectInstance;
|
9
|
+
export declare const npLicesneType: ObjectInstance;
|
10
|
+
export declare const rnSpecialtyType1: ObjectInstance;
|
11
|
+
export declare const rnSpecialtyType2: ObjectInstance;
|
12
|
+
export declare const npSpecialtyType1: ObjectInstance;
|
13
|
+
export declare const npSpecialtyType2: ObjectInstance;
|