@evoke-platform/ui-components 1.10.0-testing.0 → 1.10.0-testing.10

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 (25) 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/utils.js +1 -0
  6. package/dist/published/components/custom/FormV2/FormRenderer.js +13 -3
  7. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +0 -2
  8. package/dist/published/components/custom/FormV2/FormRendererContainer.js +10 -5
  9. package/dist/published/components/custom/FormV2/components/Footer.js +1 -0
  10. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +1 -1
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +0 -3
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +31 -27
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +1 -2
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +16 -13
  15. package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
  16. package/dist/published/components/custom/FormV2/components/Header.js +5 -2
  17. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -15
  18. package/dist/published/components/custom/FormV2/components/utils.js +2 -7
  19. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +1137 -77
  20. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +202 -12
  21. package/dist/published/components/custom/FormV2/tests/test-data.js +2 -0
  22. package/dist/published/stories/FormRendererContainer.stories.d.ts +0 -10
  23. package/dist/published/stories/FormRendererContainer.stories.js +7 -3
  24. package/dist/published/stories/FormRendererData.js +3 -43
  25. package/package.json +3 -1
@@ -1,5 +1,5 @@
1
1
  import * as matchers from '@testing-library/jest-dom/matchers';
2
- import { render, screen, waitFor, within } from '@testing-library/react';
2
+ import { render as baseRender, screen, waitFor, within } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { isEqual } from 'lodash';
5
5
  import { http, HttpResponse } from 'msw';
@@ -16,14 +16,11 @@ global.ResizeObserver = class ResizeObserver {
16
16
  unobserve() { }
17
17
  disconnect() { }
18
18
  };
19
- const removePoppers = () => {
20
- const portalSelectors = ['.MuiAutocomplete-popper'];
21
- portalSelectors.forEach((selector) => {
22
- // eslint-disable-next-line testing-library/no-node-access
23
- document.querySelectorAll(selector).forEach((el) => el.remove());
24
- });
19
+ const WithProviders = ({ children }) => {
20
+ return React.createElement(MemoryRouter, null, children);
25
21
  };
26
- describe('Form component', () => {
22
+ const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
23
+ describe('FormRendererContainer', () => {
27
24
  let server;
28
25
  beforeAll(() => {
29
26
  server = setupServer(http.get('/api/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/api/data/objects/specialtyType/effective', (req) => {
@@ -71,7 +68,6 @@ describe('Form component', () => {
71
68
  });
72
69
  afterEach(() => {
73
70
  server.resetHandlers();
74
- removePoppers();
75
71
  });
76
72
  describe('validation criteria', () => {
77
73
  it(`filters related object field with validation criteria that references a defaulted related object's nested data`, async () => {
@@ -81,13 +77,11 @@ describe('Form component', () => {
81
77
  }), http.get('/api/data/forms/specialtyForm', () => {
82
78
  return HttpResponse.json(createSpecialtyForm);
83
79
  }));
84
- render(React.createElement(MemoryRouter, null,
85
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_create', associatedObject: { propertyId: 'license', instanceId: 'rnLicense' } })));
80
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_create', associatedObject: { propertyId: 'license', instanceId: 'rnLicense' } }));
86
81
  // Validate that the license field is hidden
87
82
  await waitFor(() => expect(screen.queryByRole('combobox', { name: 'License' })).not.toBeInTheDocument());
88
83
  // Validate that specialty type dropdown is only rendering specialty types that are associated with the selected license.
89
84
  const specialtyType = await screen.findByRole('combobox', { name: 'Specialty Type' });
90
- await new Promise((r) => setTimeout(r, 5000));
91
85
  await user.click(specialtyType);
92
86
  const openAutocomplete = await screen.findByRole('listbox');
93
87
  await within(openAutocomplete).findByRole('option', { name: 'RN Specialty Type #1' });
@@ -100,4 +94,200 @@ describe('Form component', () => {
100
94
  });
101
95
  });
102
96
  });
97
+ it('should display a submit button', async () => {
98
+ const form = {
99
+ id: 'simpleForm',
100
+ name: 'Simple Form',
101
+ entries: [],
102
+ actionId: '_create',
103
+ objectId: 'simpleObject',
104
+ };
105
+ const simpleObject = {
106
+ id: 'simpleObject',
107
+ name: 'Simple Object',
108
+ actions: [
109
+ {
110
+ id: '_create',
111
+ name: 'Create',
112
+ type: 'create',
113
+ parameters: [],
114
+ outputEvent: 'created',
115
+ },
116
+ ],
117
+ properties: [],
118
+ };
119
+ server.use(http.get(`/api/data/objects/${simpleObject.id}/effective`, () => HttpResponse.json(simpleObject)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)));
120
+ render(React.createElement(FormRendererContainer, { objectId: simpleObject.id, formId: form.id, dataType: "objectInstances" }));
121
+ await screen.findByRole('button', { name: 'Submit' });
122
+ });
123
+ it('should display a button to discard changes', async () => {
124
+ const form = {
125
+ id: 'simpleForm',
126
+ name: 'Simple Form',
127
+ entries: [],
128
+ actionId: '_create',
129
+ objectId: 'simpleObject',
130
+ };
131
+ const simpleObject = {
132
+ id: 'simpleObject',
133
+ name: 'Simple Object',
134
+ actions: [
135
+ {
136
+ id: '_create',
137
+ name: 'Create',
138
+ type: 'create',
139
+ parameters: [],
140
+ outputEvent: 'created',
141
+ },
142
+ ],
143
+ properties: [],
144
+ };
145
+ server.use(http.get(`/api/data/objects/${simpleObject.id}/effective`, () => HttpResponse.json(simpleObject)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)));
146
+ render(React.createElement(FormRendererContainer, { objectId: simpleObject.id, formId: form.id, dataType: "objectInstances" }));
147
+ await screen.findByRole('button', { name: 'Discard Changes' });
148
+ });
149
+ it('should reset the form when discarding changes', async () => {
150
+ const form = {
151
+ id: 'simpleForm2',
152
+ name: 'Simple Form',
153
+ entries: [
154
+ {
155
+ type: 'inputField',
156
+ input: {
157
+ id: 'firstName',
158
+ type: 'string',
159
+ },
160
+ display: {
161
+ label: 'First Name',
162
+ },
163
+ },
164
+ ],
165
+ actionId: '_create',
166
+ objectId: 'simpleObject2',
167
+ };
168
+ const simpleObject = {
169
+ id: 'simpleObject2',
170
+ name: 'Simple Object',
171
+ actions: [
172
+ {
173
+ id: '_create',
174
+ name: 'Create',
175
+ type: 'create',
176
+ outputEvent: 'created',
177
+ },
178
+ ],
179
+ properties: [
180
+ {
181
+ id: 'firstName',
182
+ name: 'First Name',
183
+ type: 'string',
184
+ },
185
+ ],
186
+ };
187
+ server.use(http.get(`/api/data/objects/${simpleObject.id}/effective`, () => HttpResponse.json(simpleObject)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)));
188
+ const user = userEvent.setup();
189
+ render(React.createElement(FormRendererContainer, { objectId: simpleObject.id, formId: form.id, dataType: "objectInstances" }));
190
+ const firstNameInput = await screen.findByRole('textbox', { name: 'First Name' });
191
+ await user.type(firstNameInput, 'John');
192
+ const discardButton = await screen.findByRole('button', { name: 'Discard Changes' });
193
+ await user.click(discardButton);
194
+ await waitFor(() => expect(firstNameInput).toHaveValue(''));
195
+ });
196
+ describe('when submitting a form with validation', () => {
197
+ const form = {
198
+ id: 'validationTestForm',
199
+ name: 'Validation Test Form',
200
+ entries: [
201
+ {
202
+ type: 'input',
203
+ parameterId: 'requiredField',
204
+ display: {
205
+ label: 'Required Field',
206
+ required: true,
207
+ },
208
+ },
209
+ ],
210
+ actionId: '_create',
211
+ objectId: 'validationTestObject',
212
+ };
213
+ let scrollIntoViewMock;
214
+ let originalScrollIntoView;
215
+ beforeEach(() => {
216
+ scrollIntoViewMock = vitest.fn();
217
+ originalScrollIntoView = Element.prototype.scrollIntoView;
218
+ Element.prototype.scrollIntoView = scrollIntoViewMock;
219
+ });
220
+ afterEach(() => {
221
+ Element.prototype.scrollIntoView = originalScrollIntoView;
222
+ });
223
+ const validationTestObject = {
224
+ id: 'validationTestObject',
225
+ name: 'Validation Test Object',
226
+ actions: [
227
+ {
228
+ id: '_create',
229
+ name: 'Create',
230
+ type: 'create',
231
+ parameters: [
232
+ {
233
+ id: 'requiredField',
234
+ name: 'Required Field',
235
+ type: 'string',
236
+ required: true,
237
+ },
238
+ ],
239
+ outputEvent: 'created',
240
+ },
241
+ ],
242
+ properties: [
243
+ {
244
+ id: 'requiredField',
245
+ name: 'Required Field',
246
+ type: 'string',
247
+ },
248
+ ],
249
+ };
250
+ beforeEach(() => {
251
+ server.use(http.get(`/api/data/objects/${validationTestObject.id}/effective`, () => HttpResponse.json(validationTestObject)), http.get(`/api/data/forms/${form.id}`, () => HttpResponse.json(form)));
252
+ });
253
+ it('should display validation errors after trying to submit the form', async () => {
254
+ const user = userEvent.setup();
255
+ render(React.createElement(FormRendererContainer, { objectId: validationTestObject.id, formId: form.id, dataType: "objectInstances" }));
256
+ const submitButton = await screen.findByRole('button', { name: 'Submit' });
257
+ await user.click(submitButton);
258
+ // List items are named by author, but they don't
259
+ // need to be given an accessible name here because their text content is clear enough.
260
+ // As such, we use getByRole and ensure it has the correct text
261
+ const errorMessage = await screen.findByRole('listitem');
262
+ expect(errorMessage).toHaveTextContent('Required Field is required');
263
+ });
264
+ it('should clear validation errors after they have been resolved', async () => {
265
+ const user = userEvent.setup();
266
+ render(React.createElement(FormRendererContainer, { objectId: validationTestObject.id, formId: form.id, dataType: "objectInstances" }));
267
+ const submitButton = await screen.findByRole('button', { name: 'Submit' });
268
+ await user.click(submitButton);
269
+ // Make sure error elements appear
270
+ screen.getByRole('listitem');
271
+ const requiredField = screen.getByRole('textbox', { name: /Required Field */i });
272
+ await user.type(requiredField, 'Some content here...');
273
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
274
+ });
275
+ it('should scroll to validation errors after submission', async () => {
276
+ const user = userEvent.setup();
277
+ render(React.createElement(FormRendererContainer, { objectId: validationTestObject.id, formId: form.id, dataType: "objectInstances" }));
278
+ const submitButton = await screen.findByRole('button', { name: 'Submit' });
279
+ await user.click(submitButton);
280
+ expect(scrollIntoViewMock).toHaveBeenCalled();
281
+ });
282
+ it('should not scroll to validation errors after submission if there are none', async () => {
283
+ const user = userEvent.setup();
284
+ server.use(http.post(`/api/data/objects/${validationTestObject.id}/instances/actions`, () => HttpResponse.json({}, { status: 200 })));
285
+ render(React.createElement(FormRendererContainer, { objectId: validationTestObject.id, formId: form.id, dataType: "objectInstances" }));
286
+ const requiredField = await screen.findByRole('textbox', { name: /Required Field */i });
287
+ await user.type(requiredField, 'Some content here...');
288
+ const submitButton = await screen.findByRole('button', { name: 'Submit' });
289
+ await user.click(submitButton);
290
+ expect(scrollIntoViewMock).not.toHaveBeenCalled();
291
+ });
292
+ });
103
293
  });
@@ -116,6 +116,8 @@ export const UpdateAccessibilityFormOne = {
116
116
  display: {
117
117
  label: 'License',
118
118
  relatedObjectDisplay: 'dropdown',
119
+ createActionId: '_create',
120
+ createFormId: 'specialtyForm',
119
121
  },
120
122
  },
121
123
  ],
@@ -10,8 +10,6 @@ declare const _default: import("@storybook/types").ComponentAnnotations<import("
10
10
  fieldHeight?: "medium" | "small" | undefined;
11
11
  } | undefined;
12
12
  actionId?: string | undefined;
13
- stickyFooter?: boolean | undefined;
14
- hideButtons?: boolean | undefined;
15
13
  objectId: string;
16
14
  richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
17
15
  onSubmit?: ((submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>) | undefined;
@@ -39,8 +37,6 @@ export declare const Editable: import("@storybook/types").AnnotatedStoryFn<impor
39
37
  fieldHeight?: "medium" | "small" | undefined;
40
38
  } | undefined;
41
39
  actionId?: string | undefined;
42
- stickyFooter?: boolean | undefined;
43
- hideButtons?: boolean | undefined;
44
40
  objectId: string;
45
41
  richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
46
42
  onSubmit?: ((submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>) | undefined;
@@ -67,8 +63,6 @@ export declare const NoButtons: import("@storybook/types").AnnotatedStoryFn<impo
67
63
  fieldHeight?: "medium" | "small" | undefined;
68
64
  } | undefined;
69
65
  actionId?: string | undefined;
70
- stickyFooter?: boolean | undefined;
71
- hideButtons?: boolean | undefined;
72
66
  objectId: string;
73
67
  richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
74
68
  onSubmit?: ((submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>) | undefined;
@@ -95,8 +89,6 @@ export declare const DocumentForm: import("@storybook/types").AnnotatedStoryFn<i
95
89
  fieldHeight?: "medium" | "small" | undefined;
96
90
  } | undefined;
97
91
  actionId?: string | undefined;
98
- stickyFooter?: boolean | undefined;
99
- hideButtons?: boolean | undefined;
100
92
  objectId: string;
101
93
  richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
102
94
  onSubmit?: ((submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>) | undefined;
@@ -123,8 +115,6 @@ export declare const FormWithSections: import("@storybook/types").AnnotatedStory
123
115
  fieldHeight?: "medium" | "small" | undefined;
124
116
  } | undefined;
125
117
  actionId?: string | undefined;
126
- stickyFooter?: boolean | undefined;
127
- hideButtons?: boolean | undefined;
128
118
  objectId: string;
129
119
  richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
130
120
  onSubmit?: ((submission: Record<string, unknown>, defaultSubmitHandler: (submission: Record<string, unknown>) => Promise<void>) => Promise<void>) | undefined;
@@ -2,7 +2,7 @@ import { Box } from '@mui/material';
2
2
  import { http, HttpResponse } from 'msw';
3
3
  import React from 'react';
4
4
  import { MemoryRouter } from 'react-router-dom';
5
- import { FormRendererContainer } from '../components/custom';
5
+ import { FormRenderer, FormRendererContainer } from '../components/custom';
6
6
  import { documentInstance } from './FormRendererData';
7
7
  import { sharedObjectHandlers } from './sharedMswHandlers';
8
8
  export default {
@@ -31,8 +31,12 @@ const mockProps = {
31
31
  fieldHeight: 'medium',
32
32
  },
33
33
  actionId: '_update',
34
- stickyFooter: true,
35
34
  objectId: 'genericEvokeForm',
35
+ renderFooter: (footerProps) => (React.createElement(FormRenderer.Footer, { ...footerProps, sx: {
36
+ background: 'white',
37
+ position: 'sticky',
38
+ bottom: 0,
39
+ } })),
36
40
  };
37
41
  const Template = (args) => {
38
42
  return (React.createElement(MemoryRouter, null,
@@ -45,7 +49,7 @@ Editable.args = {
45
49
  export const NoButtons = Template.bind({});
46
50
  NoButtons.args = {
47
51
  ...mockProps,
48
- hideButtons: true,
52
+ renderFooter: () => null,
49
53
  };
50
54
  export const DocumentForm = Template.bind({});
51
55
  DocumentForm.args = {
@@ -196,7 +196,7 @@ export const mockMovieObject = {
196
196
  id: 'customers',
197
197
  name: 'Customers',
198
198
  type: 'collection',
199
- objectId: 'costomerMovie',
199
+ objectId: 'customerMovie',
200
200
  relatedPropertyId: 'movie',
201
201
  manyToManyPropertyId: 'customer',
202
202
  },
@@ -533,20 +533,6 @@ export const mockEvokeForm = {
533
533
  display: {
534
534
  label: 'Multi-Select',
535
535
  },
536
- enumWithLabels: [
537
- {
538
- label: '1',
539
- value: '1',
540
- },
541
- {
542
- label: '2',
543
- value: '2',
544
- },
545
- {
546
- label: '3',
547
- value: '3',
548
- },
549
- ],
550
536
  },
551
537
  {
552
538
  type: 'input',
@@ -571,6 +557,8 @@ export const mockEvokeForm = {
571
557
  parameterId: 'collection',
572
558
  display: {
573
559
  label: 'Collection',
560
+ createActionId: '_create',
561
+ createFormId: 'customerCreateForm',
574
562
  },
575
563
  },
576
564
  {
@@ -815,20 +803,6 @@ export const mockDocEvokeForm = {
815
803
  sortBy: 'ASC',
816
804
  },
817
805
  },
818
- enumWithLabels: [
819
- {
820
- label: 'Public',
821
- value: 'Public',
822
- },
823
- {
824
- label: 'Private',
825
- value: 'Private',
826
- },
827
- {
828
- label: 'Portal',
829
- value: 'Portal',
830
- },
831
- ],
832
806
  },
833
807
  {
834
808
  type: 'readonlyField',
@@ -887,20 +861,6 @@ export const mockEvokeFormWithSections = {
887
861
  display: {
888
862
  label: 'Multi-Select',
889
863
  },
890
- enumWithLabels: [
891
- {
892
- label: '1',
893
- value: '1',
894
- },
895
- {
896
- label: '2',
897
- value: '2',
898
- },
899
- {
900
- label: '3',
901
- value: '3',
902
- },
903
- ],
904
864
  },
905
865
  {
906
866
  type: 'input',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.10.0-testing.0",
3
+ "version": "1.10.0-testing.10",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "test": "vitest",
22
22
  "test:ui": "vitest --ui",
23
+ "test:preview": "vitest-preview",
23
24
  "copy-styles": "copyfiles -u 1 src/styles/*.css dist/published/",
24
25
  "build": "rm -rf ./dist && tsc && npm run copy-styles",
25
26
  "build:cjs": "tsc --module CommonJS --outDir dist/cjs",
@@ -88,6 +89,7 @@
88
89
  "storybook": "^7.6.20",
89
90
  "typescript": "^4.7.3",
90
91
  "vitest": "^1.6.0",
92
+ "vitest-preview": "^0.0.3",
91
93
  "webpack": "^5.74.0",
92
94
  "yalc": "^1.0.0-pre.53"
93
95
  },