@evoke-platform/ui-components 1.10.0-testing.8 → 1.10.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 (80) hide show
  1. package/dist/published/components/core/Autocomplete/Autocomplete.js +4 -2
  2. package/dist/published/components/core/Autocomplete/Autocomplete.test.js +112 -3
  3. package/dist/published/components/core/TextField/TextField.js +1 -1
  4. package/dist/published/components/core/TextField/TextField.test.js +0 -2
  5. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +25 -3
  6. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.d.ts +1 -0
  7. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +473 -0
  8. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +19 -6
  9. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +2 -1
  10. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  11. package/dist/published/components/custom/Form/tests/Form.test.js +0 -2
  12. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +36 -7
  13. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  14. package/dist/published/components/custom/FormField/FormField.d.ts +3 -1
  15. package/dist/published/components/custom/FormField/FormField.js +17 -5
  16. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -4
  17. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js +0 -2
  18. package/dist/published/components/custom/FormField/Select/Select.test.js +0 -2
  19. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  20. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  21. package/dist/published/components/custom/FormV2/FormRenderer.js +46 -8
  22. package/dist/published/components/custom/FormV2/FormRendererContainer.js +178 -153
  23. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  24. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  25. package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
  26. package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
  27. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  28. package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
  29. package/dist/published/components/custom/FormV2/components/Footer.js +8 -5
  30. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  32. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
  33. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +2 -2
  34. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +6 -23
  35. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
  36. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +22 -4
  37. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
  38. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
  39. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
  40. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
  41. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +115 -87
  42. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
  43. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +43 -20
  44. package/dist/published/components/custom/FormV2/components/Header.d.ts +5 -3
  45. package/dist/published/components/custom/FormV2/components/Header.js +47 -9
  46. package/dist/published/components/custom/FormV2/components/PropertyProtection.d.ts +16 -0
  47. package/dist/published/components/custom/FormV2/components/PropertyProtection.js +113 -0
  48. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +47 -24
  49. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
  50. package/dist/published/components/custom/FormV2/components/types.d.ts +2 -0
  51. package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
  52. package/dist/published/components/custom/FormV2/components/utils.js +83 -13
  53. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +413 -46
  54. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +983 -16
  55. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
  56. package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
  57. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  58. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +165 -0
  59. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
  60. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +144 -0
  61. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
  62. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  63. package/dist/published/components/custom/index.d.ts +2 -0
  64. package/dist/published/components/custom/index.js +1 -0
  65. package/dist/published/index.d.ts +6 -6
  66. package/dist/published/index.js +1 -1
  67. package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
  68. package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
  69. package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
  70. package/dist/published/stories/FormRendererContainer.stories.js +5 -0
  71. package/dist/published/stories/FormRendererData.d.ts +12 -0
  72. package/dist/published/stories/FormRendererData.js +26 -1
  73. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  74. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  75. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  76. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  77. package/dist/published/stories/sharedMswHandlers.js +49 -10
  78. package/dist/published/theme/hooks.d.ts +4 -3
  79. package/dist/published/types.d.ts +3 -0
  80. package/package.json +12 -8
@@ -1,4 +1,3 @@
1
- import * as matchers from '@testing-library/jest-dom/matchers';
2
1
  import { render as baseRender, screen, waitFor, within } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
4
3
  import { isEmpty, isEqual, set } from 'lodash';
@@ -9,7 +8,6 @@ import { MemoryRouter } from 'react-router-dom';
9
8
  import { expect, it } from 'vitest';
10
9
  import FormRenderer from '../FormRenderer';
11
10
  import { accessibility508Object, createSpecialtyForm, jsonLogicDisplayTestSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, simpleConditionDisplayTestSpecialtyForm, specialtyObject, specialtyTypeObject, UpdateAccessibilityFormOne, UpdateAccessibilityFormTwo, users, } from './test-data';
12
- expect.extend(matchers);
13
11
  // Mock ResizeObserver
14
12
  global.ResizeObserver = class ResizeObserver {
15
13
  observe() { }
@@ -20,7 +18,7 @@ const WithProviders = ({ children }) => {
20
18
  return React.createElement(MemoryRouter, null, children);
21
19
  };
22
20
  const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
23
- describe('Form component', () => {
21
+ describe('FormRenderer', () => {
24
22
  let server;
25
23
  beforeAll(() => {
26
24
  server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
@@ -59,9 +57,9 @@ describe('Form component', () => {
59
57
  npSpecialtyType1,
60
58
  npSpecialtyType2,
61
59
  ]);
62
- else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'rnLicenseType' }, {}] }))
60
+ else if (isEqual(whereFilter, { 'licenseType.id': 'rnLicenseType' }))
63
61
  return HttpResponse.json([rnSpecialtyType1, rnSpecialtyType2]);
64
- else if (isEqual(whereFilter, { and: [{ 'licenseType.id': 'npLicenseType' }, {}] }))
62
+ else if (isEqual(whereFilter, { 'licenseType.id': 'npLicenseType' }))
65
63
  return HttpResponse.json([npSpecialtyType1, npSpecialtyType2]);
66
64
  }
67
65
  }), http.get('/api/accessManagement/users', () => HttpResponse.json(users)));
@@ -365,7 +363,7 @@ describe('Form component', () => {
365
363
  });
366
364
  });
367
365
  });
368
- describe('when passed a related object entry', () => {
366
+ describe('when passed a regular related object entry', () => {
369
367
  const setupTestMocks = (object, form, instances) => {
370
368
  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 || [])));
371
369
  };
@@ -657,7 +655,12 @@ describe('Form component', () => {
657
655
  actionId: '_update',
658
656
  objectId: 'relatedObjectTestForm',
659
657
  };
658
+ let scrollIntoViewMock;
659
+ let originalScrollIntoView;
660
660
  beforeEach(async () => {
661
+ scrollIntoViewMock = vitest.fn();
662
+ originalScrollIntoView = Element.prototype.scrollIntoView;
663
+ Element.prototype.scrollIntoView = scrollIntoViewMock;
661
664
  const relatedObjectTestFormObject = {
662
665
  id: 'relatedObjectTestForm',
663
666
  name: 'Related Object Test Form',
@@ -694,9 +697,20 @@ describe('Form component', () => {
694
697
  type: 'content',
695
698
  html: '<div>Specialty Type Form Content</div>',
696
699
  },
700
+ {
701
+ type: 'input',
702
+ parameterId: 'requiredField',
703
+ display: {
704
+ label: 'Required Field',
705
+ required: true,
706
+ },
707
+ },
697
708
  ],
698
709
  actionId: '_create',
699
710
  objectId: 'specialtyType',
711
+ display: {
712
+ submitLabel: 'Create Specialty Type',
713
+ },
700
714
  };
701
715
  const specialtyTypeObject = {
702
716
  id: 'specialtyType',
@@ -706,7 +720,14 @@ describe('Form component', () => {
706
720
  id: '_create',
707
721
  name: 'Create',
708
722
  type: 'create',
709
- parameters: [],
723
+ parameters: [
724
+ {
725
+ id: 'requiredField',
726
+ name: 'Required Field',
727
+ type: 'string',
728
+ required: true,
729
+ },
730
+ ],
710
731
  outputEvent: 'created',
711
732
  defaultFormId: 'specialtyTypeForm',
712
733
  },
@@ -722,6 +743,9 @@ describe('Form component', () => {
722
743
  };
723
744
  setupTestMocks(specialtyTypeObject, specialtyTypeForm);
724
745
  });
746
+ afterEach(() => {
747
+ Element.prototype.scrollIntoView = originalScrollIntoView;
748
+ });
725
749
  it('displays an add button for related object fields', async () => {
726
750
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
727
751
  await screen.findByRole('button', { name: 'Add' });
@@ -814,6 +838,63 @@ describe('Form component', () => {
814
838
  await user.click(newRecordButton);
815
839
  await screen.findByText(/not found/i);
816
840
  });
841
+ it('should show validation errors in record creation mode', async () => {
842
+ const user = userEvent.setup();
843
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
844
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
845
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
846
+ await user.click(newRecordButton);
847
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
848
+ name: /create specialty type/i,
849
+ });
850
+ await user.click(createSpecialtyTypeButton);
851
+ const errorMessage = await screen.findByRole('listitem');
852
+ expect(errorMessage).toHaveTextContent('Required Field is required');
853
+ });
854
+ it('should clear validation errors after they have been resolved', async () => {
855
+ const user = userEvent.setup();
856
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
857
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
858
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
859
+ await user.click(newRecordButton);
860
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
861
+ name: /create specialty type/i,
862
+ });
863
+ await user.click(createSpecialtyTypeButton);
864
+ // Make sure error elements appear
865
+ screen.getByRole('listitem');
866
+ const requiredField = screen.getByRole('textbox', { name: /Required Field */i });
867
+ await user.type(requiredField, 'Some content here...');
868
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
869
+ });
870
+ it('should scroll to validation errors after submission', async () => {
871
+ const user = userEvent.setup();
872
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
873
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
874
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
875
+ await user.click(newRecordButton);
876
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
877
+ name: /create specialty type/i,
878
+ });
879
+ await user.click(createSpecialtyTypeButton);
880
+ expect(scrollIntoViewMock).toHaveBeenCalled();
881
+ });
882
+ it('should not scroll to validation errors if there are none', async () => async () => {
883
+ const user = userEvent.setup();
884
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
885
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
886
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
887
+ await user.click(newRecordButton);
888
+ // Make sure error elements appear
889
+ screen.getByRole('listitem');
890
+ const requiredField = await screen.findByRole('textbox', { name: /Required Field */i });
891
+ await user.type(requiredField, 'Some content here...');
892
+ const createSpecialtyTypeButton = await screen.findByRole('button', {
893
+ name: /create specialty type/i,
894
+ });
895
+ await user.click(createSpecialtyTypeButton);
896
+ expect(scrollIntoViewMock).not.toHaveBeenCalled();
897
+ });
817
898
  });
818
899
  });
819
900
  describe('when in dropdown view', () => {
@@ -914,25 +995,25 @@ describe('Form component', () => {
914
995
  });
915
996
  describe('when mode is default (allows both new and existing)', () => {
916
997
  const form = {
917
- id: 'relatedObjectTestForm',
998
+ id: 'dropdownRelatedObjectTestForm',
918
999
  name: 'Related Object Test Form',
919
1000
  entries: [
920
1001
  {
921
1002
  type: 'input',
922
- parameterId: 'specialtyType',
1003
+ parameterId: 'licenseType',
923
1004
  display: {
924
- label: 'Speciality Type',
1005
+ label: 'License Type',
925
1006
  relatedObjectDisplay: 'dropdown',
926
1007
  createActionId: '_create',
927
1008
  },
928
1009
  },
929
1010
  ],
930
1011
  actionId: '_update',
931
- objectId: 'relatedObjectTestForm',
1012
+ objectId: 'dropdownRelatedTestObject',
932
1013
  };
933
1014
  beforeEach(() => {
934
- const relatedObjectTestFormObject = {
935
- id: 'relatedObjectTestForm',
1015
+ const dropdownRelatedTestObject = {
1016
+ id: 'dropdownRelatedTestObject',
936
1017
  name: 'Related Object Test Form',
937
1018
  actions: [
938
1019
  {
@@ -941,10 +1022,10 @@ describe('Form component', () => {
941
1022
  type: 'update',
942
1023
  parameters: [
943
1024
  {
944
- id: 'specialtyType',
1025
+ id: 'licenseType',
945
1026
  name: 'Related Object',
946
1027
  type: 'object',
947
- objectId: 'specialtyType',
1028
+ objectId: 'licenseType',
948
1029
  },
949
1030
  ],
950
1031
  outputEvent: 'updated',
@@ -952,45 +1033,61 @@ describe('Form component', () => {
952
1033
  ],
953
1034
  properties: [
954
1035
  {
955
- id: 'specialtyType',
1036
+ id: 'licenseType',
956
1037
  name: 'Related Object',
957
1038
  type: 'object',
1039
+ objectId: 'licenseType',
958
1040
  },
959
1041
  ],
960
1042
  };
961
- setupTestMocks(relatedObjectTestFormObject, form);
962
- const specialtyTypeObject = {
963
- id: 'specialtyType',
964
- name: 'Specialty Type',
1043
+ setupTestMocks(dropdownRelatedTestObject, form);
1044
+ const licenseTypeObject = {
1045
+ id: 'licenseType',
1046
+ name: 'License Type',
965
1047
  actions: [
966
1048
  {
967
1049
  id: '_create',
968
1050
  name: 'Create',
969
1051
  type: 'create',
970
- parameters: [],
1052
+ parameters: [
1053
+ {
1054
+ type: 'string',
1055
+ id: 'name',
1056
+ name: 'License Type Name',
1057
+ },
1058
+ ],
971
1059
  outputEvent: 'created',
972
- defaultFormId: 'specialtyTypeForm',
1060
+ defaultFormId: 'licenseTypeForm',
1061
+ },
1062
+ ],
1063
+ properties: [
1064
+ {
1065
+ type: 'string',
1066
+ id: 'name',
1067
+ name: 'License Type Name',
973
1068
  },
974
1069
  ],
975
- properties: [],
976
1070
  };
977
- const specialtyTypeForm = {
978
- id: 'specialtyTypeForm',
979
- name: 'Specialty Type Form',
1071
+ const licenseTypeForm = {
1072
+ id: 'licenseTypeForm',
1073
+ name: 'License Type Form',
980
1074
  entries: [
981
1075
  {
982
- type: 'content',
983
- html: '<div>Specialty Type Form Content</div>',
1076
+ type: 'input',
1077
+ parameterId: 'name',
1078
+ display: {
1079
+ label: 'License Type Name',
1080
+ },
984
1081
  },
985
1082
  ],
986
1083
  actionId: '_create',
987
- objectId: 'specialtyType',
1084
+ objectId: 'licenseType',
988
1085
  };
989
- setupTestMocks(specialtyTypeObject, specialtyTypeForm, [
1086
+ setupTestMocks(licenseTypeObject, licenseTypeForm, [
990
1087
  {
991
- id: 'rnSpecialtyType1',
992
- name: 'RN Specialty Type #1',
993
- objectId: 'specialtyType',
1088
+ id: 'licenseType1',
1089
+ name: 'License Type #1',
1090
+ objectId: 'licenseType',
994
1091
  },
995
1092
  ]);
996
1093
  });
@@ -998,31 +1095,164 @@ describe('Form component', () => {
998
1095
  const user = userEvent.setup();
999
1096
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1000
1097
  // Navigate to and open dropdown
1001
- const dropdown = await screen.findByRole('combobox', { name: 'Speciality Type' });
1098
+ const dropdown = await screen.findByRole('combobox', { name: 'License Type' });
1002
1099
  await user.click(dropdown);
1003
- await waitFor(() => {
1004
- expect(screen.getByText('+ Add New')).toBeInTheDocument();
1005
- });
1006
- await user.click(screen.getByText('+ Add New'));
1100
+ await screen.findByRole('listbox');
1101
+ await user.click(await screen.findByRole('option', { name: '+ Add New' }));
1102
+ });
1103
+ it('opens create related object instance form in dialog when add new is clicked', async () => {
1104
+ const user = userEvent.setup();
1105
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1106
+ // Navigate to and open dropdown
1107
+ const dropdown = await screen.findByRole('combobox', { name: 'License Type' });
1108
+ await user.click(dropdown);
1109
+ await screen.findByRole('listbox');
1110
+ await user.click(await screen.findByRole('option', { name: '+ Add New' }));
1007
1111
  await screen.findByRole('dialog');
1008
- await screen.findByText('Specialty Type Form Content');
1112
+ await screen.findByRole('textbox', { name: 'License Type Name' });
1009
1113
  });
1010
1114
  it('allows related object instance selection', async () => {
1011
1115
  const user = userEvent.setup();
1012
1116
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1013
1117
  // Navigate to and open dropdown
1014
- const dropdown = await screen.findByRole('combobox', { name: 'Speciality Type' });
1118
+ const dropdown = await screen.findByRole('combobox', { name: 'License Type' });
1015
1119
  await user.click(dropdown);
1016
- const option = await screen.findByRole('option', { name: 'RN Specialty Type #1' });
1120
+ const option = await screen.findByRole('option', { name: 'License Type #1' });
1017
1121
  await user.click(option);
1018
1122
  // Verify option has been removed from the document
1019
1123
  expect(option).not.toBeInTheDocument();
1020
1124
  // Verify selection
1021
- screen.getByText('RN Specialty Type #1');
1125
+ screen.getByText('License Type #1');
1022
1126
  });
1023
1127
  });
1024
1128
  });
1025
1129
  });
1130
+ describe('when passed a dynamic related object entry', () => {
1131
+ const setupTestMocks = (object, form, instances) => {
1132
+ 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 || [])));
1133
+ };
1134
+ const form = {
1135
+ id: 'relatedObjectTestForm',
1136
+ name: 'Related Object Test Form',
1137
+ entries: [
1138
+ {
1139
+ type: 'input',
1140
+ parameterId: 'specialtyType',
1141
+ display: {
1142
+ label: 'Speciality Type',
1143
+ createActionId: '_create',
1144
+ relatedObjectId: 'specialtyType',
1145
+ },
1146
+ },
1147
+ ],
1148
+ actionId: '_update',
1149
+ objectId: 'relatedObjectTestForm',
1150
+ };
1151
+ beforeEach(async () => {
1152
+ const relatedObjectTestFormObject = {
1153
+ id: 'relatedObjectTestForm',
1154
+ name: 'Related Object Test Form',
1155
+ actions: [
1156
+ {
1157
+ id: '_update',
1158
+ name: 'Update',
1159
+ type: 'update',
1160
+ parameters: [
1161
+ {
1162
+ id: 'specialtyType',
1163
+ name: 'Related Object',
1164
+ type: 'object',
1165
+ },
1166
+ ],
1167
+ outputEvent: 'updated',
1168
+ },
1169
+ ],
1170
+ properties: [
1171
+ {
1172
+ id: 'specialtyType',
1173
+ name: 'Related Object',
1174
+ type: 'object',
1175
+ },
1176
+ ],
1177
+ };
1178
+ setupTestMocks(relatedObjectTestFormObject, form);
1179
+ const specialtyTypeForm = {
1180
+ id: 'specialtyTypeForm',
1181
+ name: 'Specialty Type Form',
1182
+ entries: [
1183
+ {
1184
+ type: 'content',
1185
+ html: '<div>Specialty Type Form Content</div>',
1186
+ },
1187
+ {
1188
+ type: 'input',
1189
+ parameterId: 'requiredField',
1190
+ display: {
1191
+ label: 'Required Field',
1192
+ required: true,
1193
+ },
1194
+ },
1195
+ ],
1196
+ actionId: '_create',
1197
+ objectId: 'specialtyType',
1198
+ display: {
1199
+ submitLabel: 'Create Specialty Type',
1200
+ },
1201
+ };
1202
+ const specialtyTypeObject = {
1203
+ id: 'specialtyType',
1204
+ name: 'Specialty Type',
1205
+ actions: [
1206
+ {
1207
+ id: '_create',
1208
+ name: 'Create',
1209
+ type: 'create',
1210
+ parameters: [
1211
+ {
1212
+ id: 'requiredField',
1213
+ name: 'Required Field',
1214
+ type: 'string',
1215
+ required: true,
1216
+ },
1217
+ ],
1218
+ outputEvent: 'created',
1219
+ defaultFormId: 'specialtyTypeForm',
1220
+ },
1221
+ {
1222
+ id: '_update',
1223
+ name: 'Update',
1224
+ type: 'update',
1225
+ parameters: [],
1226
+ outputEvent: 'updated',
1227
+ },
1228
+ ],
1229
+ properties: [],
1230
+ };
1231
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm);
1232
+ });
1233
+ it('displays form if user switches to creating a new record', async () => {
1234
+ const user = userEvent.setup();
1235
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1236
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
1237
+ await screen.findByRole('radiogroup', { name: 'Relation Type' });
1238
+ const existingRecordButton = await screen.findByRole('radio', { name: /existing/i });
1239
+ expect(existingRecordButton).toBeChecked();
1240
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
1241
+ await user.click(newRecordButton);
1242
+ await screen.findByText('Specialty Type Form Content');
1243
+ });
1244
+ it('displays a not found error in record creation mode if a form could not be found', async () => {
1245
+ const user = userEvent.setup();
1246
+ server.use(http.get('/api/data/forms/specialtyTypeForm', () => {
1247
+ return HttpResponse.json({ error: 'Not found' }, { status: 404 });
1248
+ }));
1249
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1250
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
1251
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
1252
+ await user.click(newRecordButton);
1253
+ await screen.findByText(/not found/i);
1254
+ });
1255
+ });
1026
1256
  describe('when passed a one-to-many collection entry', () => {
1027
1257
  const setupTestMocks = (object, form, instances) => {
1028
1258
  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({
@@ -1050,7 +1280,12 @@ describe('Form component', () => {
1050
1280
  actionId: '_update',
1051
1281
  objectId: 'testObjectForCollections',
1052
1282
  };
1283
+ let scrollIntoViewMock;
1284
+ let originalScrollIntoView;
1053
1285
  beforeEach(() => {
1286
+ scrollIntoViewMock = vitest.fn();
1287
+ originalScrollIntoView = Element.prototype.scrollIntoView;
1288
+ Element.prototype.scrollIntoView = scrollIntoViewMock;
1054
1289
  const collectionFormObject = {
1055
1290
  id: 'testObjectForCollections',
1056
1291
  name: 'Object for one-to-many collections tests',
@@ -1108,6 +1343,7 @@ describe('Form component', () => {
1108
1343
  objectId: 'testObjectForCollections',
1109
1344
  },
1110
1345
  ],
1346
+ defaultFormId: 'collectionObjectForm',
1111
1347
  },
1112
1348
  ],
1113
1349
  };
@@ -1120,6 +1356,7 @@ describe('Form component', () => {
1120
1356
  parameterId: 'name',
1121
1357
  display: {
1122
1358
  label: 'Name',
1359
+ required: true,
1123
1360
  },
1124
1361
  },
1125
1362
  {
@@ -1133,9 +1370,15 @@ describe('Form component', () => {
1133
1370
  ],
1134
1371
  actionId: '_create',
1135
1372
  objectId: 'collectionObject',
1373
+ display: {
1374
+ submitLabel: 'Create Collection Item',
1375
+ },
1136
1376
  };
1137
1377
  setupTestMocks(collectionObject, collectionObjectForm);
1138
1378
  });
1379
+ afterEach(() => {
1380
+ Element.prototype.scrollIntoView = originalScrollIntoView;
1381
+ });
1139
1382
  it('should render collection field', async () => {
1140
1383
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1141
1384
  await screen.findByText('Collection');
@@ -1194,7 +1437,7 @@ describe('Form component', () => {
1194
1437
  const user = userEvent.setup();
1195
1438
  render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1196
1439
  await user.click(await screen.findByRole('button', { name: 'Add' }));
1197
- await screen.findByRole('button', { name: 'Submit' });
1440
+ await screen.findByRole('button', { name: 'Create Collection Item' });
1198
1441
  });
1199
1442
  it('should hide related object field in collection item form', async () => {
1200
1443
  const user = userEvent.setup();
@@ -1203,7 +1446,7 @@ describe('Form component', () => {
1203
1446
  await user.click(addButton);
1204
1447
  await screen.findByRole('dialog');
1205
1448
  // Make sure other form entry is present
1206
- await screen.findByRole('textbox', { name: 'Name' });
1449
+ await screen.findByRole('textbox', { name: 'Name *' });
1207
1450
  const relatedObjectField = screen.queryByRole('textbox', { name: 'Related Object' });
1208
1451
  expect(relatedObjectField).not.toBeInTheDocument();
1209
1452
  });
@@ -1222,12 +1465,136 @@ describe('Form component', () => {
1222
1465
  const addButton = await screen.findByRole('button', { name: /add/i });
1223
1466
  await user.click(addButton);
1224
1467
  await screen.findByRole('dialog');
1225
- const nameField = screen.getByRole('textbox', { name: 'Name' });
1468
+ const nameField = screen.getByRole('textbox', { name: 'Name *' });
1226
1469
  await user.type(nameField, 'New Collection Item');
1227
- const submitButton = screen.getByRole('button', { name: 'Submit' });
1470
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1228
1471
  await user.click(submitButton);
1229
1472
  await screen.findByRole('columnheader', { name: 'Name' });
1230
1473
  screen.getByRole('cell', { name: 'New Collection Item' });
1231
1474
  });
1475
+ it('should show validation errors', async () => {
1476
+ const user = userEvent.setup();
1477
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1478
+ const addButton = await screen.findByRole('button', { name: /add/i });
1479
+ await user.click(addButton);
1480
+ await screen.findByRole('dialog');
1481
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1482
+ await user.click(submitButton);
1483
+ const errorMessage = await screen.findByRole('listitem');
1484
+ expect(errorMessage).toHaveTextContent('Name is required');
1485
+ });
1486
+ it('should hide validation errors after they have been resolved', async () => {
1487
+ const user = userEvent.setup();
1488
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1489
+ const addButton = await screen.findByRole('button', { name: /add/i });
1490
+ await user.click(addButton);
1491
+ await screen.findByRole('dialog');
1492
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1493
+ await user.click(submitButton);
1494
+ // Make sure error elements appear
1495
+ screen.getByRole('listitem');
1496
+ const requiredField = screen.getByRole('textbox', { name: /Name */i });
1497
+ await user.type(requiredField, 'Some content here...');
1498
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
1499
+ });
1500
+ it('should scroll to validation errors on submit', async () => {
1501
+ const user = userEvent.setup();
1502
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1503
+ const addButton = await screen.findByRole('button', { name: /add/i });
1504
+ await user.click(addButton);
1505
+ await screen.findByRole('dialog');
1506
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1507
+ await user.click(submitButton);
1508
+ expect(scrollIntoViewMock).toHaveBeenCalled();
1509
+ });
1510
+ it('should not scroll to validation errors if there are none', async () => async () => {
1511
+ const user = userEvent.setup();
1512
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1513
+ const addButton = await screen.findByRole('button', { name: /add/i });
1514
+ await user.click(addButton);
1515
+ await screen.findByRole('dialog');
1516
+ const requiredField = await screen.findByRole('textbox', { name: /Name */i });
1517
+ await user.type(requiredField, 'Some content here...');
1518
+ const submitButton = screen.getByRole('button', { name: 'Create Collection Item' });
1519
+ await user.click(submitButton);
1520
+ expect(scrollIntoViewMock).not.toHaveBeenCalled();
1521
+ });
1522
+ });
1523
+ describe('when passed a text field entry', () => {
1524
+ it('should render text field', async () => {
1525
+ const form = {
1526
+ id: 'textFieldTestForm',
1527
+ name: 'Text Field Test Form',
1528
+ entries: [
1529
+ {
1530
+ type: 'inputField',
1531
+ input: {
1532
+ id: 'textField',
1533
+ type: 'string',
1534
+ },
1535
+ display: {
1536
+ label: 'Text Field',
1537
+ },
1538
+ },
1539
+ ],
1540
+ actionId: '_update',
1541
+ objectId: 'textFieldTestObject',
1542
+ };
1543
+ const textFieldTestObject = {
1544
+ id: 'textFieldTestObject',
1545
+ name: 'Text Field Test Object',
1546
+ actions: [
1547
+ {
1548
+ id: '_update',
1549
+ name: 'Update',
1550
+ type: 'update',
1551
+ outputEvent: 'updated',
1552
+ },
1553
+ ],
1554
+ properties: [],
1555
+ };
1556
+ server.use(http.get(`/api/data/objects/${textFieldTestObject.id}/effective`, () => HttpResponse.json(textFieldTestObject)));
1557
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1558
+ await screen.findByRole('textbox', { name: 'Text Field' });
1559
+ });
1560
+ it('should allow text input in text field', async () => {
1561
+ const user = userEvent.setup();
1562
+ const form = {
1563
+ id: 'textFieldTestForm',
1564
+ name: 'Text Field Test Form',
1565
+ entries: [
1566
+ {
1567
+ type: 'inputField',
1568
+ input: {
1569
+ id: 'textField',
1570
+ type: 'string',
1571
+ },
1572
+ display: {
1573
+ label: 'Text Field',
1574
+ },
1575
+ },
1576
+ ],
1577
+ actionId: '_update',
1578
+ objectId: 'textFieldTestObject',
1579
+ };
1580
+ const textFieldTestObject = {
1581
+ id: 'textFieldTestObject',
1582
+ name: 'Text Field Test Object',
1583
+ actions: [
1584
+ {
1585
+ id: '_update',
1586
+ name: 'Update',
1587
+ type: 'update',
1588
+ outputEvent: 'updated',
1589
+ },
1590
+ ],
1591
+ properties: [],
1592
+ };
1593
+ server.use(http.get(`/api/data/objects/${textFieldTestObject.id}/effective`, () => HttpResponse.json(textFieldTestObject)));
1594
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1595
+ const textField = await screen.findByRole('textbox', { name: 'Text Field' });
1596
+ await user.type(textField, 'Test Input');
1597
+ expect(textField).toHaveValue('Test Input');
1598
+ });
1232
1599
  });
1233
1600
  });