@evoke-platform/ui-components 1.10.0-dev.2 → 1.10.0-dev.21

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 (64) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +1 -1
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.d.ts +1 -0
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +430 -0
  4. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +19 -6
  5. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  6. package/dist/published/components/custom/Form/utils.js +1 -0
  7. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +14 -1
  8. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  9. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  10. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  11. package/dist/published/components/custom/FormV2/FormRenderer.js +17 -4
  12. package/dist/published/components/custom/FormV2/FormRendererContainer.js +116 -74
  13. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  14. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  15. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  16. package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
  17. package/dist/published/components/custom/FormV2/components/Footer.js +3 -3
  18. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +1 -1
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +0 -3
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +36 -49
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +16 -4
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
  30. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +109 -81
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +38 -16
  32. package/dist/published/components/custom/FormV2/components/Header.d.ts +13 -3
  33. package/dist/published/components/custom/FormV2/components/Header.js +47 -8
  34. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +44 -35
  35. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
  36. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
  37. package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
  38. package/dist/published/components/custom/FormV2/components/utils.js +11 -14
  39. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +433 -4
  40. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +662 -13
  41. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
  42. package/dist/published/components/custom/FormV2/tests/test-data.js +140 -0
  43. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  44. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
  45. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
  46. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +140 -0
  47. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
  48. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  49. package/dist/published/components/custom/index.d.ts +2 -0
  50. package/dist/published/components/custom/index.js +1 -0
  51. package/dist/published/index.d.ts +6 -6
  52. package/dist/published/index.js +1 -1
  53. package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
  54. package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
  55. package/dist/published/stories/FormRendererContainer.stories.js +5 -0
  56. package/dist/published/stories/FormRendererData.d.ts +12 -0
  57. package/dist/published/stories/FormRendererData.js +27 -44
  58. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  59. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  60. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  61. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  62. package/dist/published/stories/sharedMswHandlers.js +49 -10
  63. package/dist/published/theme/hooks.d.ts +4 -3
  64. package/package.json +4 -2
@@ -10,7 +10,7 @@ export declare function isAddressProperty(key: string): boolean;
10
10
  /**
11
11
  * Determine if a form entry is visible or not.
12
12
  */
13
- export declare const entryIsVisible: (entry: FormEntry, formValues: FieldValues, instance?: FieldValues) => boolean;
13
+ export declare const entryIsVisible: (entry: FormEntry, instance?: FieldValues, formValues?: FieldValues) => boolean;
14
14
  /**
15
15
  * Recursively retrieves all parameter IDs from a given entry of type Sections or Columns.
16
16
  *
@@ -86,7 +86,7 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
86
86
  message?: string;
87
87
  isError: boolean;
88
88
  }>>): Promise<FieldValues>;
89
- export declare function filterEmptySections(entry: Sections | Columns, formData: FieldValues, instance?: FieldValues): Sections | Columns | null;
89
+ export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
90
90
  export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object?: Obj, parameters?: InputParameter[]): FormEntry[];
91
91
  /**
92
92
  * Converts a plain text string to RTF format suitable for a RichTextEditor.
@@ -68,7 +68,7 @@ export function isAddressProperty(key) {
68
68
  /**
69
69
  * Determine if a form entry is visible or not.
70
70
  */
71
- export const entryIsVisible = (entry, formValues, instance) => {
71
+ export const entryIsVisible = (entry, instance, formValues) => {
72
72
  const display = 'display' in entry ? entry.display : undefined;
73
73
  const { visibility } = display ?? ('visibility' in entry ? entry : {});
74
74
  if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
@@ -482,11 +482,6 @@ export function convertDocToEntries(document) {
482
482
  sortBy: 'ASC',
483
483
  },
484
484
  },
485
- enumWithLabels: [
486
- { label: 'Public', value: 'Public' },
487
- { label: 'Private', value: 'Private' },
488
- { label: 'Portal', value: 'Portal' },
489
- ],
490
485
  });
491
486
  }
492
487
  entries.push({
@@ -507,8 +502,8 @@ export function formatDataToDoc(data) {
507
502
  uploadedDate: data.uploadedDate,
508
503
  versionId: data.versionId,
509
504
  metadata: {
510
- type: data.type,
511
- view_permission: data.view_permission,
505
+ type: data.type ?? '',
506
+ view_permission: data.view_permission ?? '',
512
507
  },
513
508
  };
514
509
  }
@@ -567,6 +562,7 @@ export const uploadDocuments = async (files, metadata, apiServices, instanceId,
567
562
  const allDocuments = [];
568
563
  const formData = new FormData();
569
564
  for (const [index, file] of files.entries()) {
565
+ // Only upload File instances; SavedDocumentReference objects are already uploaded
570
566
  if ('size' in file) {
571
567
  formData.append(`files[${index}]`, file);
572
568
  }
@@ -628,6 +624,7 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
628
624
  export async function formatSubmission(submission, apiServices, objectId, instanceId, form, setSnackbarError) {
629
625
  for (const [key, value] of Object.entries(submission)) {
630
626
  if (isArray(value)) {
627
+ // Only upload if array contains File instances (not SavedDocumentReference)
631
628
  const fileInArray = value.some((item) => item instanceof File);
632
629
  if (fileInArray && instanceId && apiServices && objectId) {
633
630
  try {
@@ -672,21 +669,21 @@ export async function formatSubmission(submission, apiServices, objectId, instan
672
669
  }
673
670
  return submission;
674
671
  }
675
- export function filterEmptySections(entry, formData, instance) {
672
+ export function filterEmptySections(entry, instance, formData) {
676
673
  if (entry.type === 'sections' && isArray(entry.sections)) {
677
674
  const visibleSections = entry.sections.filter((section) => {
678
675
  if (!section.entries || section.entries.length === 0)
679
676
  return false;
680
677
  for (const sectionEntry of section.entries) {
681
678
  if (sectionEntry.type === 'sections' || sectionEntry.type === 'columns') {
682
- if (sectionEntry.visibility && !entryIsVisible(sectionEntry, formData, instance)) {
679
+ if (sectionEntry.visibility && !entryIsVisible(sectionEntry, instance, formData)) {
683
680
  return false;
684
681
  }
685
- else if (filterEmptySections(sectionEntry, formData, instance)) {
682
+ else if (filterEmptySections(sectionEntry, instance, formData)) {
686
683
  return true;
687
684
  }
688
685
  }
689
- else if (entryIsVisible(sectionEntry, formData, instance)) {
686
+ else if (entryIsVisible(sectionEntry, instance, formData)) {
690
687
  return true;
691
688
  }
692
689
  }
@@ -706,13 +703,13 @@ export function filterEmptySections(entry, formData, instance) {
706
703
  let hasVisibleEntry = false;
707
704
  for (const columnEntry of column.entries) {
708
705
  if (columnEntry.type === 'sections' || columnEntry.type === 'columns') {
709
- if (filterEmptySections(columnEntry, formData, instance)) {
706
+ if (filterEmptySections(columnEntry, instance, formData)) {
710
707
  hasVisibleEntry = true;
711
708
  break;
712
709
  }
713
710
  }
714
711
  else {
715
- if (entryIsVisible(columnEntry, formData, instance)) {
712
+ if (entryIsVisible(columnEntry, instance, formData)) {
716
713
  hasVisibleEntry = true;
717
714
  break;
718
715
  }
@@ -20,7 +20,7 @@ const WithProviders = ({ children }) => {
20
20
  return React.createElement(MemoryRouter, null, children);
21
21
  };
22
22
  const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
23
- describe('Form component', () => {
23
+ describe('FormRenderer', () => {
24
24
  let server;
25
25
  beforeAll(() => {
26
26
  server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
@@ -59,9 +59,9 @@ describe('Form component', () => {
59
59
  npSpecialtyType1,
60
60
  npSpecialtyType2,
61
61
  ]);
62
- else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'rnLicenseType' }, {}] }))
62
+ else if (isEqual(whereFilter, { 'licenseType.id': 'rnLicenseType' }))
63
63
  return HttpResponse.json([rnSpecialtyType1, rnSpecialtyType2]);
64
- else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'npLicenseType' }, {}] }))
64
+ else if (isEqual(whereFilter, { 'licenseType.id': 'npLicenseType' }))
65
65
  return HttpResponse.json([npSpecialtyType1, npSpecialtyType2]);
66
66
  }
67
67
  }), http.get('/api/accessManagement/users', () => HttpResponse.json(users)));
@@ -650,13 +650,19 @@ describe('Form component', () => {
650
650
  parameterId: 'specialtyType',
651
651
  display: {
652
652
  label: 'Speciality Type',
653
+ createActionId: '_create',
653
654
  },
654
655
  },
655
656
  ],
656
657
  actionId: '_update',
657
658
  objectId: 'relatedObjectTestForm',
658
659
  };
660
+ let scrollIntoViewMock;
661
+ let originalScrollIntoView;
659
662
  beforeEach(async () => {
663
+ scrollIntoViewMock = vitest.fn();
664
+ originalScrollIntoView = Element.prototype.scrollIntoView;
665
+ Element.prototype.scrollIntoView = scrollIntoViewMock;
660
666
  const relatedObjectTestFormObject = {
661
667
  id: 'relatedObjectTestForm',
662
668
  name: 'Related Object Test Form',
@@ -693,9 +699,20 @@ describe('Form component', () => {
693
699
  type: 'content',
694
700
  html: '<div>Specialty Type Form Content</div>',
695
701
  },
702
+ {
703
+ type: 'input',
704
+ parameterId: 'requiredField',
705
+ display: {
706
+ label: 'Required Field',
707
+ required: true,
708
+ },
709
+ },
696
710
  ],
697
711
  actionId: '_create',
698
712
  objectId: 'specialtyType',
713
+ display: {
714
+ submitLabel: 'Create Specialty Type',
715
+ },
699
716
  };
700
717
  const specialtyTypeObject = {
701
718
  id: 'specialtyType',
@@ -705,7 +722,14 @@ describe('Form component', () => {
705
722
  id: '_create',
706
723
  name: 'Create',
707
724
  type: 'create',
708
- parameters: [],
725
+ parameters: [
726
+ {
727
+ id: 'requiredField',
728
+ name: 'Required Field',
729
+ type: 'string',
730
+ required: true,
731
+ },
732
+ ],
709
733
  outputEvent: 'created',
710
734
  defaultFormId: 'specialtyTypeForm',
711
735
  },
@@ -721,6 +745,9 @@ describe('Form component', () => {
721
745
  };
722
746
  setupTestMocks(specialtyTypeObject, specialtyTypeForm);
723
747
  });
748
+ afterEach(() => {
749
+ Element.prototype.scrollIntoView = originalScrollIntoView;
750
+ });
724
751
  it('displays an add button for related object fields', async () => {
725
752
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
726
753
  await screen.findByRole('button', { name: 'Add' });
@@ -813,6 +840,63 @@ describe('Form component', () => {
813
840
  await user.click(newRecordButton);
814
841
  await screen.findByText(/not found/i);
815
842
  });
843
+ it('should show validation errors in record creation mode', async () => {
844
+ const user = userEvent.setup();
845
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
846
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
847
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
848
+ await user.click(newRecordButton);
849
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
850
+ name: /create specialty type/i,
851
+ });
852
+ await user.click(createSpecialtyTypeButton);
853
+ const errorMessage = await screen.findByRole('listitem');
854
+ expect(errorMessage).toHaveTextContent('Required Field is required');
855
+ });
856
+ it('should clear validation errors after they have been resolved', async () => {
857
+ const user = userEvent.setup();
858
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
859
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
860
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
861
+ await user.click(newRecordButton);
862
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
863
+ name: /create specialty type/i,
864
+ });
865
+ await user.click(createSpecialtyTypeButton);
866
+ // Make sure error elements appear
867
+ screen.getByRole('listitem');
868
+ const requiredField = screen.getByRole('textbox', { name: /Required Field */i });
869
+ await user.type(requiredField, 'Some content here...');
870
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
871
+ });
872
+ it('should scroll to validation errors after submission', async () => {
873
+ const user = userEvent.setup();
874
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
875
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
876
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
877
+ await user.click(newRecordButton);
878
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
879
+ name: /create specialty type/i,
880
+ });
881
+ await user.click(createSpecialtyTypeButton);
882
+ expect(scrollIntoViewMock).toHaveBeenCalled();
883
+ });
884
+ it('should not scroll to validation errors if there are none', async () => async () => {
885
+ const user = userEvent.setup();
886
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
887
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
888
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
889
+ await user.click(newRecordButton);
890
+ // Make sure error elements appear
891
+ screen.getByRole('listitem');
892
+ const requiredField = await screen.findByRole('textbox', { name: /Required Field */i });
893
+ await user.type(requiredField, 'Some content here...');
894
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
895
+ name: /create specialty type/i,
896
+ });
897
+ await user.click(createSpecialtyTypeButton);
898
+ expect(scrollIntoViewMock).not.toHaveBeenCalled();
899
+ });
816
900
  });
817
901
  });
818
902
  describe('when in dropdown view', () => {
@@ -922,6 +1006,7 @@ describe('Form component', () => {
922
1006
  display: {
923
1007
  label: 'Speciality Type',
924
1008
  relatedObjectDisplay: 'dropdown',
1009
+ createActionId: '_create',
925
1010
  },
926
1011
  },
927
1012
  ],
@@ -1021,4 +1106,348 @@ describe('Form component', () => {
1021
1106
  });
1022
1107
  });
1023
1108
  });
1109
+ describe('when passed a one-to-many collection entry', () => {
1110
+ const setupTestMocks = (object, form, instances) => {
1111
+ server.use(http.get(`/api/data/objects/${object.id}/effective`, () => HttpResponse.json(object)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)), http.get(`/api/data/forms?filter={"where":{"actionId":"${form.actionId}","objectId":"${object.id}"}}`, () => HttpResponse.json([form])), http.get(`/api/data/objects/${object.id}/instances`, () => HttpResponse.json(instances || [])), http.get(`/api/data/objects/${object.id}/instances/checkAccess`, () => HttpResponse.json({
1112
+ result: true,
1113
+ })));
1114
+ };
1115
+ const form = {
1116
+ id: 'collectionTestForm',
1117
+ name: 'Collection Test Form',
1118
+ entries: [
1119
+ {
1120
+ type: 'inputField',
1121
+ input: {
1122
+ id: 'collection',
1123
+ type: 'collection',
1124
+ objectId: 'collectionObject',
1125
+ relatedPropertyId: 'relatedObject',
1126
+ },
1127
+ display: {
1128
+ label: 'Collection',
1129
+ createActionId: '_create',
1130
+ },
1131
+ },
1132
+ ],
1133
+ actionId: '_update',
1134
+ objectId: 'testObjectForCollections',
1135
+ };
1136
+ let scrollIntoViewMock;
1137
+ let originalScrollIntoView;
1138
+ beforeEach(() => {
1139
+ scrollIntoViewMock = vitest.fn();
1140
+ originalScrollIntoView = Element.prototype.scrollIntoView;
1141
+ Element.prototype.scrollIntoView = scrollIntoViewMock;
1142
+ const collectionFormObject = {
1143
+ id: 'testObjectForCollections',
1144
+ name: 'Object for one-to-many collections tests',
1145
+ actions: [
1146
+ {
1147
+ id: '_update',
1148
+ name: 'Update',
1149
+ type: 'update',
1150
+ outputEvent: 'updated',
1151
+ },
1152
+ ],
1153
+ properties: [
1154
+ {
1155
+ id: 'collection',
1156
+ name: 'Collection',
1157
+ type: 'collection',
1158
+ objectId: 'collectionObject',
1159
+ relatedPropertyId: 'relatedObject',
1160
+ },
1161
+ ],
1162
+ };
1163
+ setupTestMocks(collectionFormObject, form);
1164
+ const collectionObject = {
1165
+ id: 'collectionObject',
1166
+ name: 'Collection Object',
1167
+ properties: [
1168
+ {
1169
+ name: 'Name',
1170
+ id: 'name',
1171
+ type: 'string',
1172
+ },
1173
+ {
1174
+ name: 'Related Object',
1175
+ id: 'relatedObject',
1176
+ type: 'object',
1177
+ objectId: 'testObjectForCollections',
1178
+ },
1179
+ ],
1180
+ actions: [
1181
+ {
1182
+ id: '_create',
1183
+ name: 'Create',
1184
+ type: 'create',
1185
+ outputEvent: 'created',
1186
+ parameters: [
1187
+ {
1188
+ id: 'name',
1189
+ name: 'Name',
1190
+ type: 'string',
1191
+ },
1192
+ {
1193
+ id: 'relatedObject',
1194
+ name: 'Related Object',
1195
+ type: 'object',
1196
+ objectId: 'testObjectForCollections',
1197
+ },
1198
+ ],
1199
+ defaultFormId: 'collectionObjectForm',
1200
+ },
1201
+ ],
1202
+ };
1203
+ const collectionObjectForm = {
1204
+ id: 'collectionObjectForm',
1205
+ name: 'Collection Object Form',
1206
+ entries: [
1207
+ {
1208
+ type: 'input',
1209
+ parameterId: 'name',
1210
+ display: {
1211
+ label: 'Name',
1212
+ required: true,
1213
+ },
1214
+ },
1215
+ {
1216
+ type: 'input',
1217
+ parameterId: 'relatedObject',
1218
+ display: {
1219
+ label: 'Related Object',
1220
+ relatedObjectDisplay: 'dropdown',
1221
+ },
1222
+ },
1223
+ ],
1224
+ actionId: '_create',
1225
+ objectId: 'collectionObject',
1226
+ display: {
1227
+ submitLabel: 'Create Collection Item',
1228
+ },
1229
+ };
1230
+ setupTestMocks(collectionObject, collectionObjectForm);
1231
+ });
1232
+ afterEach(() => {
1233
+ Element.prototype.scrollIntoView = originalScrollIntoView;
1234
+ });
1235
+ it('should render collection field', async () => {
1236
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1237
+ await screen.findByText('Collection');
1238
+ });
1239
+ it('should not render an add button if user does not have permission to create collection items', async () => {
1240
+ server.use(http.get('/api/data/objects/collectionObject/instances/checkAccess', () => HttpResponse.json({
1241
+ result: false,
1242
+ })));
1243
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1244
+ const addButton = screen.queryByRole('button', { name: /add/i });
1245
+ expect(addButton).not.toBeInTheDocument();
1246
+ });
1247
+ it('should render an add button for the collection when user has permission to create collection items', async () => {
1248
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1249
+ await screen.findByRole('button', { name: /add/i });
1250
+ });
1251
+ it('should open a dialog when clicking the add button', async () => {
1252
+ const user = userEvent.setup();
1253
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1254
+ const addButton = await screen.findByRole('button', { name: /add/i });
1255
+ await user.click(addButton);
1256
+ await screen.findByRole('dialog');
1257
+ });
1258
+ it('should render a close button in the collection dialog', async () => {
1259
+ const user = userEvent.setup();
1260
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1261
+ const addButton = await screen.findByRole('button', { name: /add/i });
1262
+ await user.click(addButton);
1263
+ await screen.findByRole('button', { name: 'Close' });
1264
+ });
1265
+ it('should allow closing the collection dialog using the close button', async () => {
1266
+ const user = userEvent.setup();
1267
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1268
+ const addButton = await screen.findByRole('button', { name: /add/i });
1269
+ await user.click(addButton);
1270
+ const dialog = await screen.findByRole('dialog');
1271
+ const closeButton = await screen.findByRole('button', { name: 'Close' });
1272
+ await user.click(closeButton);
1273
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
1274
+ });
1275
+ it('shows a cancel button in the collection dialog', async () => {
1276
+ const user = userEvent.setup();
1277
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1278
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
1279
+ await screen.findByRole('button', { name: 'Cancel' });
1280
+ });
1281
+ it('closes dialog in the collection dialog clicking cancel', async () => {
1282
+ const user = userEvent.setup();
1283
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1284
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
1285
+ const dialog = await screen.findByRole('dialog');
1286
+ await user.click(await screen.findByRole('button', { name: 'Cancel' }));
1287
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
1288
+ });
1289
+ it('displays a submit button in the collection dialog', async () => {
1290
+ const user = userEvent.setup();
1291
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1292
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
1293
+ await screen.findByRole('button', { name: 'Create Collection Item' });
1294
+ });
1295
+ it('should hide related object field in collection item form', async () => {
1296
+ const user = userEvent.setup();
1297
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1298
+ const addButton = await screen.findByRole('button', { name: /add/i });
1299
+ await user.click(addButton);
1300
+ await screen.findByRole('dialog');
1301
+ // Make sure other form entry is present
1302
+ await screen.findByRole('textbox', { name: 'Name *' });
1303
+ const relatedObjectField = screen.queryByRole('textbox', { name: 'Related Object' });
1304
+ expect(relatedObjectField).not.toBeInTheDocument();
1305
+ });
1306
+ it('should add newly created collection item to the collection list upon submission', async () => {
1307
+ const user = userEvent.setup();
1308
+ server.use(http.post('/api/data/objects/collectionObject/instances/actions', () => HttpResponse.json({
1309
+ id: 'newCollectionItemId',
1310
+ name: 'New Collection Item',
1311
+ objectId: 'collectionObject',
1312
+ relatedObject: {
1313
+ id: 'testInstanceId',
1314
+ name: 'Test Instance',
1315
+ },
1316
+ })));
1317
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1318
+ const addButton = await screen.findByRole('button', { name: /add/i });
1319
+ await user.click(addButton);
1320
+ await screen.findByRole('dialog');
1321
+ const nameField = screen.getByRole('textbox', { name: 'Name *' });
1322
+ await user.type(nameField, 'New Collection Item');
1323
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1324
+ await user.click(submitButton);
1325
+ await screen.findByRole('columnheader', { name: 'Name' });
1326
+ screen.getByRole('cell', { name: 'New Collection Item' });
1327
+ });
1328
+ it('should show validation errors', async () => {
1329
+ const user = userEvent.setup();
1330
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1331
+ const addButton = await screen.findByRole('button', { name: /add/i });
1332
+ await user.click(addButton);
1333
+ await screen.findByRole('dialog');
1334
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1335
+ await user.click(submitButton);
1336
+ const errorMessage = await screen.findByRole('listitem');
1337
+ expect(errorMessage).toHaveTextContent('Name is required');
1338
+ });
1339
+ it('should hide validation errors after they have been resolved', async () => {
1340
+ const user = userEvent.setup();
1341
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1342
+ const addButton = await screen.findByRole('button', { name: /add/i });
1343
+ await user.click(addButton);
1344
+ await screen.findByRole('dialog');
1345
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1346
+ await user.click(submitButton);
1347
+ // Make sure error elements appear
1348
+ screen.getByRole('listitem');
1349
+ const requiredField = screen.getByRole('textbox', { name: /Name */i });
1350
+ await user.type(requiredField, 'Some content here...');
1351
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
1352
+ });
1353
+ it('should scroll to validation errors on submit', async () => {
1354
+ const user = userEvent.setup();
1355
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1356
+ const addButton = await screen.findByRole('button', { name: /add/i });
1357
+ await user.click(addButton);
1358
+ await screen.findByRole('dialog');
1359
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1360
+ await user.click(submitButton);
1361
+ expect(scrollIntoViewMock).toHaveBeenCalled();
1362
+ });
1363
+ it('should not scroll to validation errors if there are none', async () => async () => {
1364
+ const user = userEvent.setup();
1365
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1366
+ const addButton = await screen.findByRole('button', { name: /add/i });
1367
+ await user.click(addButton);
1368
+ await screen.findByRole('dialog');
1369
+ const requiredField = await screen.findByRole('textbox', { name: /Name */i });
1370
+ await user.type(requiredField, 'Some content here...');
1371
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1372
+ await user.click(submitButton);
1373
+ expect(scrollIntoViewMock).not.toHaveBeenCalled();
1374
+ });
1375
+ });
1376
+ describe('when passed a text field entry', () => {
1377
+ it('should render text field', async () => {
1378
+ const form = {
1379
+ id: 'textFieldTestForm',
1380
+ name: 'Text Field Test Form',
1381
+ entries: [
1382
+ {
1383
+ type: 'inputField',
1384
+ input: {
1385
+ id: 'textField',
1386
+ type: 'string',
1387
+ },
1388
+ display: {
1389
+ label: 'Text Field',
1390
+ },
1391
+ },
1392
+ ],
1393
+ actionId: '_update',
1394
+ objectId: 'textFieldTestObject',
1395
+ };
1396
+ const textFieldTestObject = {
1397
+ id: 'textFieldTestObject',
1398
+ name: 'Text Field Test Object',
1399
+ actions: [
1400
+ {
1401
+ id: '_update',
1402
+ name: 'Update',
1403
+ type: 'update',
1404
+ outputEvent: 'updated',
1405
+ },
1406
+ ],
1407
+ properties: [],
1408
+ };
1409
+ server.use(http.get(`/api/data/objects/${textFieldTestObject.id}/effective`, () => HttpResponse.json(textFieldTestObject)));
1410
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1411
+ await screen.findByRole('textbox', { name: 'Text Field' });
1412
+ });
1413
+ it('should allow text input in text field', async () => {
1414
+ const user = userEvent.setup();
1415
+ const form = {
1416
+ id: 'textFieldTestForm',
1417
+ name: 'Text Field Test Form',
1418
+ entries: [
1419
+ {
1420
+ type: 'inputField',
1421
+ input: {
1422
+ id: 'textField',
1423
+ type: 'string',
1424
+ },
1425
+ display: {
1426
+ label: 'Text Field',
1427
+ },
1428
+ },
1429
+ ],
1430
+ actionId: '_update',
1431
+ objectId: 'textFieldTestObject',
1432
+ };
1433
+ const textFieldTestObject = {
1434
+ id: 'textFieldTestObject',
1435
+ name: 'Text Field Test Object',
1436
+ actions: [
1437
+ {
1438
+ id: '_update',
1439
+ name: 'Update',
1440
+ type: 'update',
1441
+ outputEvent: 'updated',
1442
+ },
1443
+ ],
1444
+ properties: [],
1445
+ };
1446
+ server.use(http.get(`/api/data/objects/${textFieldTestObject.id}/effective`, () => HttpResponse.json(textFieldTestObject)));
1447
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1448
+ const textField = await screen.findByRole('textbox', { name: 'Text Field' });
1449
+ await user.type(textField, 'Test Input');
1450
+ expect(textField).toHaveValue('Test Input');
1451
+ });
1452
+ });
1024
1453
  });