@evoke-platform/ui-components 1.10.0-testing.10 → 1.10.0-testing.12
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/custom/FormV2/FormRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +3 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +82 -13
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +3 -3
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +30 -13
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +16 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +70 -18
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +37 -15
- package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Header.js +42 -4
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +31 -6
- package/dist/published/components/custom/FormV2/components/utils.js +2 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +449 -1
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
- package/dist/published/theme/hooks.d.ts +4 -3
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
import { MemoryRouter } from 'react-router-dom';
|
|
9
9
|
import { expect, it } from 'vitest';
|
|
10
10
|
import FormRendererContainer from '../FormRendererContainer';
|
|
11
|
-
import { createSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
|
|
11
|
+
import { createSpecialtyForm, licenseForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
|
|
12
12
|
expect.extend(matchers);
|
|
13
13
|
// Mock ResizeObserver
|
|
14
14
|
global.ResizeObserver = class ResizeObserver {
|
|
@@ -94,6 +94,454 @@ describe('FormRendererContainer', () => {
|
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
|
+
describe('autosave functionality', () => {
|
|
98
|
+
it('should trigger autosave when field loses focus', async () => {
|
|
99
|
+
const user = userEvent.setup();
|
|
100
|
+
const autosaveActionSpy = vi.fn();
|
|
101
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
102
|
+
return HttpResponse.json({
|
|
103
|
+
id: 'test-instance',
|
|
104
|
+
name: 'Original Name',
|
|
105
|
+
specialtyType: null,
|
|
106
|
+
license: null,
|
|
107
|
+
});
|
|
108
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
109
|
+
return HttpResponse.json(specialtyObject);
|
|
110
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
111
|
+
return HttpResponse.json({
|
|
112
|
+
...createSpecialtyForm,
|
|
113
|
+
actionId: '_update',
|
|
114
|
+
autosaveActionId: '_autosave',
|
|
115
|
+
});
|
|
116
|
+
}), http.post('/api/data/objects/specialty/instances/test-instance/actions', async ({ request }) => {
|
|
117
|
+
const body = (await request.json());
|
|
118
|
+
autosaveActionSpy(body);
|
|
119
|
+
return HttpResponse.json({
|
|
120
|
+
id: 'test-instance',
|
|
121
|
+
name: body.input.name,
|
|
122
|
+
specialtyType: null,
|
|
123
|
+
license: null,
|
|
124
|
+
});
|
|
125
|
+
}));
|
|
126
|
+
render(React.createElement(MemoryRouter, null,
|
|
127
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
const nameField = await screen.findByRole('textbox', { name: 'Name' });
|
|
132
|
+
// Clear the existing value and type new value
|
|
133
|
+
await user.clear(nameField);
|
|
134
|
+
await user.type(nameField, 'Test Specialty');
|
|
135
|
+
await user.tab(); // Blur the field
|
|
136
|
+
// Verify the data being saved
|
|
137
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
138
|
+
actionId: '_autosave',
|
|
139
|
+
input: expect.objectContaining({
|
|
140
|
+
name: 'Test Specialty',
|
|
141
|
+
}),
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
it('should show saving indicator during autosave', async () => {
|
|
145
|
+
const user = userEvent.setup();
|
|
146
|
+
let resolveSave;
|
|
147
|
+
const savePromise = new Promise((resolve) => {
|
|
148
|
+
resolveSave = resolve;
|
|
149
|
+
});
|
|
150
|
+
const autosaveActionSpy = vi.fn();
|
|
151
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
152
|
+
return HttpResponse.json({
|
|
153
|
+
id: 'test-instance',
|
|
154
|
+
name: '',
|
|
155
|
+
specialtyType: null,
|
|
156
|
+
license: null,
|
|
157
|
+
});
|
|
158
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
159
|
+
return HttpResponse.json(specialtyObject);
|
|
160
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
161
|
+
return HttpResponse.json({
|
|
162
|
+
...createSpecialtyForm,
|
|
163
|
+
actionId: '_update',
|
|
164
|
+
autosaveActionId: '_autosave',
|
|
165
|
+
});
|
|
166
|
+
}), http.post('/api/data/objects/specialty/instances/test-instance/actions', async ({ request }) => {
|
|
167
|
+
const body = (await request.json());
|
|
168
|
+
autosaveActionSpy(body);
|
|
169
|
+
await savePromise; // Wait for manual resolution
|
|
170
|
+
return HttpResponse.json({
|
|
171
|
+
id: 'test-instance',
|
|
172
|
+
name: body.input.name,
|
|
173
|
+
specialtyType: null,
|
|
174
|
+
license: null,
|
|
175
|
+
});
|
|
176
|
+
}));
|
|
177
|
+
render(React.createElement(MemoryRouter, null,
|
|
178
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
const nameField = await screen.findByRole('textbox', { name: 'Name' });
|
|
183
|
+
await user.type(nameField, 'Test Specialty');
|
|
184
|
+
await user.tab();
|
|
185
|
+
// Should show saving indicator
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(screen.getByText('Saving')).toBeInTheDocument();
|
|
188
|
+
});
|
|
189
|
+
// Verify the API was called
|
|
190
|
+
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
191
|
+
// Complete the save
|
|
192
|
+
resolveSave({
|
|
193
|
+
id: 'test-instance',
|
|
194
|
+
name: 'Test Specialty',
|
|
195
|
+
specialtyType: null,
|
|
196
|
+
license: null,
|
|
197
|
+
});
|
|
198
|
+
// Saving indicator should disappear
|
|
199
|
+
await waitFor(() => {
|
|
200
|
+
expect(screen.queryByText('Saving')).not.toBeInTheDocument();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
it('should hide saving indicator after autosave fails', async () => {
|
|
204
|
+
const user = userEvent.setup();
|
|
205
|
+
const autosaveActionSpy = vi.fn();
|
|
206
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
207
|
+
return HttpResponse.json({
|
|
208
|
+
id: 'test-instance',
|
|
209
|
+
name: '',
|
|
210
|
+
specialtyType: null,
|
|
211
|
+
license: null,
|
|
212
|
+
});
|
|
213
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
214
|
+
return HttpResponse.json(specialtyObject);
|
|
215
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
216
|
+
return HttpResponse.json({
|
|
217
|
+
...createSpecialtyForm,
|
|
218
|
+
actionId: '_update',
|
|
219
|
+
autosaveActionId: '_autosave',
|
|
220
|
+
});
|
|
221
|
+
}), http.post('/api/data/objects/specialty/instances/test-instance/actions', () => {
|
|
222
|
+
autosaveActionSpy();
|
|
223
|
+
return HttpResponse.json({ error: 'Save failed' }, { status: 500 });
|
|
224
|
+
}));
|
|
225
|
+
render(React.createElement(MemoryRouter, null,
|
|
226
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
const nameField = await screen.findByRole('textbox', { name: 'Name' });
|
|
231
|
+
await user.type(nameField, 'Test Specialty');
|
|
232
|
+
await user.tab();
|
|
233
|
+
// Wait for autosave to complete
|
|
234
|
+
await waitFor(() => {
|
|
235
|
+
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
// Saving indicator should be hidden after error
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(screen.queryByText('Saving')).not.toBeInTheDocument();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
it('should not trigger autosave when field value has not changed', async () => {
|
|
243
|
+
const user = userEvent.setup();
|
|
244
|
+
const autosaveActionSpy = vi.fn();
|
|
245
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
246
|
+
return HttpResponse.json({
|
|
247
|
+
id: 'test-instance',
|
|
248
|
+
name: 'Original Name',
|
|
249
|
+
specialtyType: null,
|
|
250
|
+
license: null,
|
|
251
|
+
});
|
|
252
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
253
|
+
return HttpResponse.json(specialtyObject);
|
|
254
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
255
|
+
return HttpResponse.json({
|
|
256
|
+
...createSpecialtyForm,
|
|
257
|
+
actionId: '_update',
|
|
258
|
+
autosaveActionId: '_autosave',
|
|
259
|
+
});
|
|
260
|
+
}), http.post('/api/data/objects/specialty/instances/test-instance/actions', async ({ request }) => {
|
|
261
|
+
const body = (await request.json());
|
|
262
|
+
autosaveActionSpy(body);
|
|
263
|
+
return HttpResponse.json({
|
|
264
|
+
id: 'test-instance',
|
|
265
|
+
name: body.input.name,
|
|
266
|
+
specialtyType: null,
|
|
267
|
+
license: null,
|
|
268
|
+
});
|
|
269
|
+
}));
|
|
270
|
+
render(React.createElement(MemoryRouter, null,
|
|
271
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
274
|
+
});
|
|
275
|
+
const nameField = await screen.findByRole('textbox', { name: 'Name' });
|
|
276
|
+
// Click into the field and blur it without changing value
|
|
277
|
+
await user.click(nameField);
|
|
278
|
+
await user.tab(); // Blur the field
|
|
279
|
+
await waitFor(() => {
|
|
280
|
+
expect(nameField).not.toHaveFocus();
|
|
281
|
+
});
|
|
282
|
+
// Wait a bit to ensure no autosave is triggered
|
|
283
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
284
|
+
// Verify autosave was NOT called since value didn't change
|
|
285
|
+
expect(autosaveActionSpy).not.toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
it('should trigger autosave when address field loses focus after change', async () => {
|
|
288
|
+
const user = userEvent.setup();
|
|
289
|
+
const autosaveActionSpy = vi.fn();
|
|
290
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => {
|
|
291
|
+
return HttpResponse.json({
|
|
292
|
+
id: 'test-license',
|
|
293
|
+
name: 'RN-123456',
|
|
294
|
+
address: {
|
|
295
|
+
line1: '123 Main St',
|
|
296
|
+
city: 'Boston',
|
|
297
|
+
state: 'MA',
|
|
298
|
+
zipCode: '02101',
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}), http.get('/api/data/objects/license/instances/test-license/object', () => {
|
|
302
|
+
return HttpResponse.json(licenseObject);
|
|
303
|
+
}), http.get('/api/data/forms/licenseForm', () => {
|
|
304
|
+
return HttpResponse.json({
|
|
305
|
+
...licenseForm,
|
|
306
|
+
autosaveActionId: '_autosave',
|
|
307
|
+
});
|
|
308
|
+
}), http.get('/api/data/locations/search', () => {
|
|
309
|
+
return HttpResponse.json([]);
|
|
310
|
+
}), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
311
|
+
const body = (await request.json());
|
|
312
|
+
autosaveActionSpy(body);
|
|
313
|
+
return HttpResponse.json({
|
|
314
|
+
id: 'test-license',
|
|
315
|
+
name: body.input.name || 'RN-123456',
|
|
316
|
+
address: body.input.address || {
|
|
317
|
+
line1: '123 Main St',
|
|
318
|
+
city: 'Boston',
|
|
319
|
+
state: 'MA',
|
|
320
|
+
zipCode: '02101',
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}));
|
|
324
|
+
render(React.createElement(MemoryRouter, null,
|
|
325
|
+
React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
|
|
326
|
+
await waitFor(() => {
|
|
327
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
328
|
+
});
|
|
329
|
+
// Find the city field
|
|
330
|
+
const cityField = await screen.findByRole('textbox', { name: 'City' });
|
|
331
|
+
// Clear and type new value
|
|
332
|
+
await user.clear(cityField);
|
|
333
|
+
await user.type(cityField, 'Cambridge');
|
|
334
|
+
await user.tab(); // Blur the field
|
|
335
|
+
// Verify autosave was eventually called and the final call contains the updated city
|
|
336
|
+
await waitFor(() => {
|
|
337
|
+
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
338
|
+
});
|
|
339
|
+
const lastCall = autosaveActionSpy.mock.lastCall[0];
|
|
340
|
+
expect(lastCall).toEqual(expect.objectContaining({
|
|
341
|
+
input: expect.objectContaining({
|
|
342
|
+
address: expect.objectContaining({
|
|
343
|
+
city: 'Cambridge',
|
|
344
|
+
}),
|
|
345
|
+
}),
|
|
346
|
+
}));
|
|
347
|
+
});
|
|
348
|
+
it('should not trigger autosave when address field loses focus without changes', async () => {
|
|
349
|
+
const user = userEvent.setup();
|
|
350
|
+
const autosaveActionSpy = vi.fn();
|
|
351
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => {
|
|
352
|
+
return HttpResponse.json({
|
|
353
|
+
id: 'test-license',
|
|
354
|
+
name: 'RN-123456',
|
|
355
|
+
address: {
|
|
356
|
+
line1: '123 Main St',
|
|
357
|
+
city: 'Boston',
|
|
358
|
+
state: 'MA',
|
|
359
|
+
zipCode: '02101',
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
}), http.get('/api/data/objects/license/instances/test-license/object', () => {
|
|
363
|
+
return HttpResponse.json(licenseObject);
|
|
364
|
+
}), http.get('/api/data/objects/license/effective', (req) => {
|
|
365
|
+
const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
|
|
366
|
+
if (sanitizedVersion === 'true') {
|
|
367
|
+
return HttpResponse.json(licenseObject);
|
|
368
|
+
}
|
|
369
|
+
}), http.get('/api/data/forms/licenseForm', () => {
|
|
370
|
+
return HttpResponse.json({
|
|
371
|
+
...licenseForm,
|
|
372
|
+
autosaveActionId: '_autosave',
|
|
373
|
+
});
|
|
374
|
+
}), http.get('/api/data/locations/search', () => {
|
|
375
|
+
return HttpResponse.json([]);
|
|
376
|
+
}), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
377
|
+
const body = (await request.json());
|
|
378
|
+
autosaveActionSpy(body);
|
|
379
|
+
return HttpResponse.json({
|
|
380
|
+
id: 'test-license',
|
|
381
|
+
name: 'RN-123456',
|
|
382
|
+
address: body.input.address,
|
|
383
|
+
});
|
|
384
|
+
}));
|
|
385
|
+
render(React.createElement(MemoryRouter, null,
|
|
386
|
+
React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
|
|
387
|
+
await waitFor(() => {
|
|
388
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
389
|
+
});
|
|
390
|
+
// Find the city field
|
|
391
|
+
const cityField = await screen.findByRole('textbox', { name: 'City' });
|
|
392
|
+
// Click into field and blur without changing
|
|
393
|
+
await user.click(cityField);
|
|
394
|
+
await user.tab();
|
|
395
|
+
// Wait to ensure no autosave is triggered
|
|
396
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
397
|
+
// Verify autosave was NOT called since value didn't change
|
|
398
|
+
expect(autosaveActionSpy).not.toHaveBeenCalled();
|
|
399
|
+
});
|
|
400
|
+
it('should trigger autosave for multiple address fields when line1 autocompletes', async () => {
|
|
401
|
+
const user = userEvent.setup();
|
|
402
|
+
const autosaveActionSpy = vi.fn();
|
|
403
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => {
|
|
404
|
+
return HttpResponse.json({
|
|
405
|
+
id: 'test-license',
|
|
406
|
+
name: 'RN-123456',
|
|
407
|
+
address: {
|
|
408
|
+
line1: '',
|
|
409
|
+
city: '',
|
|
410
|
+
state: '',
|
|
411
|
+
zipCode: '',
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}), http.get('/api/data/objects/license/instances/test-license/object', () => {
|
|
415
|
+
return HttpResponse.json(licenseObject);
|
|
416
|
+
}), http.get('/api/data/objects/license/effective', (req) => {
|
|
417
|
+
const sanitizedVersion = new URL(req.request.url).searchParams.get('sanitizedVersion');
|
|
418
|
+
if (sanitizedVersion === 'true') {
|
|
419
|
+
return HttpResponse.json(licenseObject);
|
|
420
|
+
}
|
|
421
|
+
}), http.get('/api/data/forms/licenseForm', () => {
|
|
422
|
+
return HttpResponse.json({
|
|
423
|
+
...licenseForm,
|
|
424
|
+
autosaveActionId: '_autosave',
|
|
425
|
+
});
|
|
426
|
+
}), http.get('/api/data/locations/search', () => {
|
|
427
|
+
return HttpResponse.json([
|
|
428
|
+
{
|
|
429
|
+
address: {
|
|
430
|
+
line1: '456 Oak Street',
|
|
431
|
+
city: 'Springfield',
|
|
432
|
+
state: 'MA',
|
|
433
|
+
zipCode: '01101',
|
|
434
|
+
county: 'Hampden',
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
]);
|
|
438
|
+
}), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
439
|
+
const body = (await request.json());
|
|
440
|
+
autosaveActionSpy(body);
|
|
441
|
+
return HttpResponse.json({
|
|
442
|
+
id: 'test-license',
|
|
443
|
+
name: 'RN-123456',
|
|
444
|
+
address: body.input.address,
|
|
445
|
+
});
|
|
446
|
+
}));
|
|
447
|
+
render(React.createElement(MemoryRouter, null,
|
|
448
|
+
React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
|
|
449
|
+
await waitFor(() => {
|
|
450
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
451
|
+
});
|
|
452
|
+
// Find the line1 field (it's a searchbox because of address autocomplete)
|
|
453
|
+
const line1Field = await screen.findByRole('searchbox', { name: 'Address Line 1' });
|
|
454
|
+
// Type to trigger autocomplete
|
|
455
|
+
await user.type(line1Field, '456');
|
|
456
|
+
// Wait for and select the autocomplete option
|
|
457
|
+
const autocompleteOption = await screen.findByText('456 Oak Street');
|
|
458
|
+
await user.click(autocompleteOption);
|
|
459
|
+
// Verify autosave was eventually called and the final call contains the expected address values
|
|
460
|
+
await waitFor(() => {
|
|
461
|
+
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
// The autosave is triggered twice when selecting the autocomplete option,
|
|
464
|
+
// once by the selection and once by the onBlur event. We want to verify the last call
|
|
465
|
+
// has the correct data.
|
|
466
|
+
const lastCall = autosaveActionSpy.mock.lastCall[0];
|
|
467
|
+
expect(lastCall).toEqual(expect.objectContaining({
|
|
468
|
+
input: expect.objectContaining({
|
|
469
|
+
address: expect.objectContaining({
|
|
470
|
+
line1: '456 Oak Street',
|
|
471
|
+
city: 'Springfield',
|
|
472
|
+
state: 'MA',
|
|
473
|
+
zipCode: '01101',
|
|
474
|
+
}),
|
|
475
|
+
}),
|
|
476
|
+
}));
|
|
477
|
+
});
|
|
478
|
+
it('should hide discard changes button when autosave is configured', async () => {
|
|
479
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
480
|
+
return HttpResponse.json({
|
|
481
|
+
id: 'test-instance',
|
|
482
|
+
name: '',
|
|
483
|
+
specialtyType: null,
|
|
484
|
+
license: null,
|
|
485
|
+
});
|
|
486
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
487
|
+
return HttpResponse.json(specialtyObject);
|
|
488
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
489
|
+
return HttpResponse.json({
|
|
490
|
+
...createSpecialtyForm,
|
|
491
|
+
actionId: '_update',
|
|
492
|
+
autosaveActionId: '_autosave',
|
|
493
|
+
});
|
|
494
|
+
}));
|
|
495
|
+
render(React.createElement(MemoryRouter, null,
|
|
496
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
497
|
+
await waitFor(() => {
|
|
498
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
499
|
+
});
|
|
500
|
+
// When autosaveActionId is present the discard button should be hidden
|
|
501
|
+
expect(screen.queryByRole('button', { name: /discard/i })).not.toBeInTheDocument();
|
|
502
|
+
});
|
|
503
|
+
it('should not trigger autosave when field changes but auto save is not configured', async () => {
|
|
504
|
+
server.use(http.get('/api/data/objects/specialty/instances/test-instance', () => {
|
|
505
|
+
return HttpResponse.json({
|
|
506
|
+
id: 'test-instance',
|
|
507
|
+
name: '',
|
|
508
|
+
specialtyType: null,
|
|
509
|
+
license: null,
|
|
510
|
+
});
|
|
511
|
+
}), http.get('/api/data/objects/specialty/instances/test-instance/object', () => {
|
|
512
|
+
return HttpResponse.json(specialtyObject);
|
|
513
|
+
}), http.get('/api/data/forms/specialtyForm', () => {
|
|
514
|
+
return HttpResponse.json({
|
|
515
|
+
...createSpecialtyForm,
|
|
516
|
+
actionId: '_update',
|
|
517
|
+
});
|
|
518
|
+
}));
|
|
519
|
+
const autosaveActionSpy = vi.fn();
|
|
520
|
+
server.use(http.post('/api/data/objects/specialty/instances/test-instance/actions', async ({ request }) => {
|
|
521
|
+
const body = (await request.json());
|
|
522
|
+
autosaveActionSpy(body);
|
|
523
|
+
return HttpResponse.json({
|
|
524
|
+
id: 'test-instance',
|
|
525
|
+
...body.input,
|
|
526
|
+
});
|
|
527
|
+
}));
|
|
528
|
+
const user = userEvent.setup();
|
|
529
|
+
render(React.createElement(MemoryRouter, null,
|
|
530
|
+
React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
|
|
531
|
+
await waitFor(() => {
|
|
532
|
+
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
|
533
|
+
});
|
|
534
|
+
// Change a field value
|
|
535
|
+
const nameInput = screen.getByRole('textbox', { name: /name/i });
|
|
536
|
+
await user.clear(nameInput);
|
|
537
|
+
await user.type(nameInput, 'Test Specialty');
|
|
538
|
+
await user.tab();
|
|
539
|
+
// Wait a bit to ensure no autosave happens
|
|
540
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
541
|
+
// Verify autosave was not triggered
|
|
542
|
+
expect(autosaveActionSpy).not.toHaveBeenCalled();
|
|
543
|
+
});
|
|
544
|
+
});
|
|
97
545
|
it('should display a submit button', async () => {
|
|
98
546
|
const form = {
|
|
99
547
|
id: 'simpleForm',
|
|
@@ -166,6 +166,11 @@ export const licenseObject = {
|
|
|
166
166
|
type: 'object',
|
|
167
167
|
objectId: 'licenseType',
|
|
168
168
|
},
|
|
169
|
+
{
|
|
170
|
+
id: 'address',
|
|
171
|
+
name: 'Licensee Address',
|
|
172
|
+
type: 'address',
|
|
173
|
+
},
|
|
169
174
|
],
|
|
170
175
|
actions: [
|
|
171
176
|
{
|
|
@@ -173,6 +178,96 @@ export const licenseObject = {
|
|
|
173
178
|
name: 'Update',
|
|
174
179
|
type: 'update',
|
|
175
180
|
outputEvent: 'License Updated',
|
|
181
|
+
parameters: [
|
|
182
|
+
{
|
|
183
|
+
id: 'name',
|
|
184
|
+
name: 'License Number',
|
|
185
|
+
type: 'string',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'status',
|
|
189
|
+
name: 'Status',
|
|
190
|
+
type: 'string',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'address.line1',
|
|
194
|
+
name: 'Address Line 1',
|
|
195
|
+
type: 'string',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'address.line2',
|
|
199
|
+
name: 'Address Line 2',
|
|
200
|
+
type: 'string',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'address.city',
|
|
204
|
+
name: 'City',
|
|
205
|
+
type: 'string',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'address.county',
|
|
209
|
+
name: 'County',
|
|
210
|
+
type: 'string',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'address.state',
|
|
214
|
+
name: 'State',
|
|
215
|
+
type: 'string',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'address.zipCode',
|
|
219
|
+
name: 'Zip Code',
|
|
220
|
+
type: 'string',
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: '_autosave',
|
|
226
|
+
name: 'Autosave',
|
|
227
|
+
type: 'update',
|
|
228
|
+
outputEvent: 'License Autosaved',
|
|
229
|
+
parameters: [
|
|
230
|
+
{
|
|
231
|
+
id: 'name',
|
|
232
|
+
name: 'License Number',
|
|
233
|
+
type: 'string',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'status',
|
|
237
|
+
name: 'Status',
|
|
238
|
+
type: 'string',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'address.line1',
|
|
242
|
+
name: 'Address Line 1',
|
|
243
|
+
type: 'string',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: 'address.line2',
|
|
247
|
+
name: 'Address Line 2',
|
|
248
|
+
type: 'string',
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'address.city',
|
|
252
|
+
name: 'City',
|
|
253
|
+
type: 'string',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'address.county',
|
|
257
|
+
name: 'County',
|
|
258
|
+
type: 'string',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'address.state',
|
|
262
|
+
name: 'State',
|
|
263
|
+
type: 'string',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'address.zipCode',
|
|
267
|
+
name: 'Zip Code',
|
|
268
|
+
type: 'string',
|
|
269
|
+
},
|
|
270
|
+
],
|
|
176
271
|
},
|
|
177
272
|
{
|
|
178
273
|
id: '_delete',
|
|
@@ -528,3 +623,46 @@ export const users = [
|
|
|
528
623
|
name: 'User 2',
|
|
529
624
|
},
|
|
530
625
|
];
|
|
626
|
+
export const licenseForm = {
|
|
627
|
+
id: 'licenseForm',
|
|
628
|
+
name: 'License Form',
|
|
629
|
+
objectId: 'license',
|
|
630
|
+
actionId: '_update',
|
|
631
|
+
entries: [
|
|
632
|
+
{
|
|
633
|
+
parameterId: 'name',
|
|
634
|
+
type: 'input',
|
|
635
|
+
display: {
|
|
636
|
+
label: 'License Number',
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
parameterId: 'address.line1',
|
|
641
|
+
type: 'input',
|
|
642
|
+
display: {
|
|
643
|
+
label: 'Address Line 1',
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
parameterId: 'address.city',
|
|
648
|
+
type: 'input',
|
|
649
|
+
display: {
|
|
650
|
+
label: 'City',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
parameterId: 'address.state',
|
|
655
|
+
type: 'input',
|
|
656
|
+
display: {
|
|
657
|
+
label: 'State',
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
parameterId: 'address.zipCode',
|
|
662
|
+
type: 'input',
|
|
663
|
+
display: {
|
|
664
|
+
label: 'Zip Code',
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
};
|
|
@@ -9,7 +9,8 @@ declare const _default: import("@storybook/types").ComponentAnnotations<import("
|
|
|
9
9
|
form: import("@evoke-platform/context").EvokeForm;
|
|
10
10
|
title?: React.ReactNode;
|
|
11
11
|
instance?: import("../components/custom/FormV2/components/types").Document | import("@evoke-platform/context").ObjectInstance | undefined;
|
|
12
|
-
onChange: (id: string, value: unknown) => void
|
|
12
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
13
|
+
onAutosave?: ((fieldId: string) => void | Promise<void>) | undefined;
|
|
13
14
|
associatedObject?: {
|
|
14
15
|
instanceId?: string | undefined;
|
|
15
16
|
propertyId?: string | undefined;
|
|
@@ -29,7 +30,8 @@ export declare const Editable: import("@storybook/types").AnnotatedStoryFn<impor
|
|
|
29
30
|
form: import("@evoke-platform/context").EvokeForm;
|
|
30
31
|
title?: React.ReactNode;
|
|
31
32
|
instance?: import("../components/custom/FormV2/components/types").Document | import("@evoke-platform/context").ObjectInstance | undefined;
|
|
32
|
-
onChange: (id: string, value: unknown) => void
|
|
33
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
34
|
+
onAutosave?: ((fieldId: string) => void | Promise<void>) | undefined;
|
|
33
35
|
associatedObject?: {
|
|
34
36
|
instanceId?: string | undefined;
|
|
35
37
|
propertyId?: string | undefined;
|
|
@@ -48,7 +50,8 @@ export declare const NoButtons: import("@storybook/types").AnnotatedStoryFn<impo
|
|
|
48
50
|
form: import("@evoke-platform/context").EvokeForm;
|
|
49
51
|
title?: React.ReactNode;
|
|
50
52
|
instance?: import("../components/custom/FormV2/components/types").Document | import("@evoke-platform/context").ObjectInstance | undefined;
|
|
51
|
-
onChange: (id: string, value: unknown) => void
|
|
53
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
54
|
+
onAutosave?: ((fieldId: string) => void | Promise<void>) | undefined;
|
|
52
55
|
associatedObject?: {
|
|
53
56
|
instanceId?: string | undefined;
|
|
54
57
|
propertyId?: string | undefined;
|
|
@@ -67,7 +70,8 @@ export declare const DocumentForm: import("@storybook/types").AnnotatedStoryFn<i
|
|
|
67
70
|
form: import("@evoke-platform/context").EvokeForm;
|
|
68
71
|
title?: React.ReactNode;
|
|
69
72
|
instance?: import("../components/custom/FormV2/components/types").Document | import("@evoke-platform/context").ObjectInstance | undefined;
|
|
70
|
-
onChange: (id: string, value: unknown) => void
|
|
73
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
74
|
+
onAutosave?: ((fieldId: string) => void | Promise<void>) | undefined;
|
|
71
75
|
associatedObject?: {
|
|
72
76
|
instanceId?: string | undefined;
|
|
73
77
|
propertyId?: string | undefined;
|