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

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.
@@ -2,13 +2,14 @@ import { useApiServices } from '@evoke-platform/context';
2
2
  import { Clear, Search } from '@mui/icons-material';
3
3
  import { debounce, get, startCase } from 'lodash';
4
4
  import { DateTime } from 'luxon';
5
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
- import { Button, IconButton, InputAdornment, TextField, Typography, } from '../../../../../core';
5
+ import React, { useCallback, useEffect, useId, useMemo, useState } from 'react';
6
+ import { Button, IconButton, InputAdornment, InputLabel, TextField, Typography, } from '../../../../../core';
7
7
  import { Box, Grid } from '../../../../../layout';
8
8
  import BuilderGrid from '../../../../BuilderGrid';
9
9
  import { getPrefixedUrl } from '../../utils';
10
10
  const SearchField = (props) => {
11
11
  const { searchableColumns, filter, setFilter, searchString, setSearchString } = props;
12
+ const id = useId();
12
13
  const clearSearch = () => {
13
14
  setSearchString('');
14
15
  setFilter(undefined);
@@ -37,17 +38,19 @@ const SearchField = (props) => {
37
38
  });
38
39
  }
39
40
  };
40
- return (React.createElement(TextField, { autoFocus: true, placeholder: "Search", value: searchString, onChange: (e) => handleSearch(e), size: "medium", fullWidth: true, sx: { marginBottom: '15px', marginTop: '15px' }, InputProps: {
41
- endAdornment: (React.createElement(InputAdornment, { position: "end" },
42
- React.createElement(IconButton, { sx: {
43
- visibility: searchString.length > 0 ? 'visible' : 'hidden',
44
- }, onClick: () => clearSearch() },
45
- React.createElement(Clear, { sx: {
46
- fontSize: '22px',
47
- } })))),
48
- startAdornment: (React.createElement(InputAdornment, { position: "start" },
49
- React.createElement(Search, { sx: { fontSize: '22px', color: '#637381' } }))),
50
- } }));
41
+ return (React.createElement(React.Fragment, null,
42
+ React.createElement(InputLabel, { htmlFor: id }, "Search"),
43
+ React.createElement(TextField, { id: id, autoFocus: true, placeholder: "Search", value: searchString, onChange: (e) => handleSearch(e), size: "medium", fullWidth: true, sx: { marginBottom: '15px' }, InputProps: {
44
+ endAdornment: (React.createElement(InputAdornment, { position: "end" },
45
+ React.createElement(IconButton, { sx: {
46
+ visibility: searchString.length > 0 ? 'visible' : 'hidden',
47
+ }, onClick: () => clearSearch() },
48
+ React.createElement(Clear, { sx: {
49
+ fontSize: '22px',
50
+ } })))),
51
+ startAdornment: (React.createElement(InputAdornment, { position: "start" },
52
+ React.createElement(Search, { sx: { fontSize: '22px', color: '#637381' } }))),
53
+ } })));
51
54
  };
52
55
  const InstanceLookup = (props) => {
53
56
  const { object, setSelectedInstance, setRelationType, filter: criteriaFilter, mode, layout } = props;
@@ -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 { isEmpty, isEqual, set } from 'lodash';
5
5
  import { http, HttpResponse } from 'msw';
@@ -16,13 +16,10 @@ 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
  };
22
+ const render = (ui, options) => baseRender(ui, { wrapper: WithProviders, ...options });
26
23
  describe('Form component', () => {
27
24
  let server;
28
25
  beforeAll(() => {
@@ -75,7 +72,6 @@ describe('Form component', () => {
75
72
  });
76
73
  afterEach(() => {
77
74
  server.resetHandlers();
78
- removePoppers();
79
75
  });
80
76
  describe('validation criteria', () => {
81
77
  it(`filters related object field with validation criteria that references a related object's nested data`, async () => {
@@ -103,8 +99,7 @@ describe('Form component', () => {
103
99
  return React.createElement("div", null, "Render error");
104
100
  }
105
101
  };
106
- render(React.createElement(MemoryRouter, null,
107
- React.createElement(FormWithState, null)));
102
+ render(React.createElement(FormWithState, null));
108
103
  const license = await screen.findByRole('combobox', { name: 'License' });
109
104
  // Step 1: Open Specialty Type and verify all options
110
105
  await user.click(await screen.findByRole('combobox', { name: 'Specialty Type' }));
@@ -134,23 +129,21 @@ describe('Form component', () => {
134
129
  describe('visibility configuration', () => {
135
130
  it('shows fields based on instance data using JsonLogic', async () => {
136
131
  server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
137
- render(React.createElement(MemoryRouter, null,
138
- React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
139
- id: '123',
140
- objectId: 'specialty',
141
- name: 'Test Specialty Object Instance',
142
- } })));
132
+ render(React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
133
+ id: '123',
134
+ objectId: 'specialty',
135
+ name: 'Test Specialty Object Instance',
136
+ } }));
143
137
  // Validate that specialty type dropdown renders
144
138
  await screen.findByRole('combobox', { name: 'Specialty Type' });
145
139
  });
146
140
  it('hides fields based on instance data using JsonLogic', async () => {
147
141
  server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
148
- render(React.createElement(MemoryRouter, null,
149
- React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
150
- id: '123',
151
- objectId: 'specialty',
152
- name: 'Test Specialty Object Instance -- hidden',
153
- } })));
142
+ render(React.createElement(FormRenderer, { form: jsonLogicDisplayTestSpecialtyForm, onChange: () => { }, instance: {
143
+ id: '123',
144
+ objectId: 'specialty',
145
+ name: 'Test Specialty Object Instance -- hidden',
146
+ } }));
154
147
  // Validate that license dropdown renders
155
148
  await screen.findByRole('combobox', { name: 'License' });
156
149
  // Validate that specialty type dropdown does not render
@@ -158,23 +151,21 @@ describe('Form component', () => {
158
151
  });
159
152
  it('shows fields based on instance data using simple conditions', async () => {
160
153
  server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
161
- render(React.createElement(MemoryRouter, null,
162
- React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
163
- id: '123',
164
- objectId: 'specialty',
165
- name: 'Test Specialty Object Instance',
166
- } })));
154
+ render(React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
155
+ id: '123',
156
+ objectId: 'specialty',
157
+ name: 'Test Specialty Object Instance',
158
+ } }));
167
159
  // Validate that specialty type dropdown renders
168
160
  await screen.findByRole('combobox', { name: 'Specialty Type' });
169
161
  });
170
162
  it('hides fields based on instance data using simple conditions', async () => {
171
163
  server.use(http.get('/data/objects/license/instances/rnLicense', () => HttpResponse.json(rnLicense)));
172
- render(React.createElement(MemoryRouter, null,
173
- React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
174
- id: '123',
175
- objectId: 'specialty',
176
- name: 'Test Specialty Object Instance -- hidden',
177
- } })));
164
+ render(React.createElement(FormRenderer, { form: simpleConditionDisplayTestSpecialtyForm, onChange: () => { }, instance: {
165
+ id: '123',
166
+ objectId: 'specialty',
167
+ name: 'Test Specialty Object Instance -- hidden',
168
+ } }));
178
169
  // Validate that license dropdown renders
179
170
  await screen.findByRole('combobox', { name: 'License' });
180
171
  // Validate that specialty type dropdown does not render
@@ -185,9 +176,7 @@ describe('Form component', () => {
185
176
  describe('508 accessibility compliance', () => {
186
177
  it('supports keyboard navigation back and forth through Related Object dropdowns', async () => {
187
178
  const user = userEvent.setup();
188
- render(React.createElement(MemoryRouter, null,
189
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }),
190
- ","));
179
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }));
191
180
  await waitFor(() => {
192
181
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
193
182
  });
@@ -203,9 +192,7 @@ describe('Form component', () => {
203
192
  });
204
193
  it('supports keyboard navigation back and forth through User dropdowns', async () => {
205
194
  const user = userEvent.setup();
206
- render(React.createElement(MemoryRouter, null,
207
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }),
208
- ","));
195
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }));
209
196
  await waitFor(() => {
210
197
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
211
198
  });
@@ -223,9 +210,7 @@ describe('Form component', () => {
223
210
  });
224
211
  it('supports keyboard selection of dropdown values using Enter key on Related Objects', async () => {
225
212
  const user = userEvent.setup();
226
- render(React.createElement(MemoryRouter, null,
227
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }),
228
- ","));
213
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }));
229
214
  await waitFor(() => {
230
215
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
231
216
  });
@@ -246,9 +231,7 @@ describe('Form component', () => {
246
231
  });
247
232
  it('supports keyboard selection of dropdown values using Enter key on Users', async () => {
248
233
  const user = userEvent.setup();
249
- render(React.createElement(MemoryRouter, null,
250
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }),
251
- ","));
234
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }));
252
235
  await waitFor(() => {
253
236
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
254
237
  });
@@ -268,9 +251,7 @@ describe('Form component', () => {
268
251
  });
269
252
  it('supports navigating between dropdown options using arrow keys on Related Objects', async () => {
270
253
  const user = userEvent.setup();
271
- render(React.createElement(MemoryRouter, null,
272
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }),
273
- ","));
254
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }));
274
255
  await waitFor(() => {
275
256
  expect(screen.getByLabelText('License')).toBeInTheDocument();
276
257
  });
@@ -290,9 +271,7 @@ describe('Form component', () => {
290
271
  });
291
272
  it('supports navigating between dropdown options using arrow keys on User dropdowns', async () => {
292
273
  const user = userEvent.setup();
293
- render(React.createElement(MemoryRouter, null,
294
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }),
295
- ","));
274
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { } }));
296
275
  await waitFor(() => {
297
276
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
298
277
  });
@@ -310,16 +289,15 @@ describe('Form component', () => {
310
289
  });
311
290
  it('supports clearing selection with the clear button on Related Objects', async () => {
312
291
  const user = userEvent.setup();
313
- render(React.createElement(MemoryRouter, null,
314
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { }, value: {
315
- id: '123',
316
- objectId: 'accessibility508Object',
317
- name: 'Test Accessibility 508 Object Instance',
318
- license: {
319
- id: 'rnLicense',
320
- name: 'RN License',
321
- },
322
- } })));
292
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { }, value: {
293
+ id: '123',
294
+ objectId: 'accessibility508Object',
295
+ name: 'Test Accessibility 508 Object Instance',
296
+ license: {
297
+ id: 'rnLicense',
298
+ name: 'RN License',
299
+ },
300
+ } }));
323
301
  // Set up a selection first
324
302
  await waitFor(() => {
325
303
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
@@ -339,16 +317,15 @@ describe('Form component', () => {
339
317
  });
340
318
  it('supports clearing selection with the clear button on User dropdowns', async () => {
341
319
  const user = userEvent.setup();
342
- render(React.createElement(MemoryRouter, null,
343
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { }, value: {
344
- id: '123',
345
- objectId: 'accessibility508Object',
346
- name: 'Test Accessibility 508 Object Instance',
347
- user: {
348
- id: 'user1',
349
- name: 'User 1',
350
- },
351
- } })));
320
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormTwo, onChange: () => { }, value: {
321
+ id: '123',
322
+ objectId: 'accessibility508Object',
323
+ name: 'Test Accessibility 508 Object Instance',
324
+ user: {
325
+ id: 'user1',
326
+ name: 'User 1',
327
+ },
328
+ } }));
352
329
  // Set up a selection first
353
330
  await waitFor(() => {
354
331
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
@@ -370,8 +347,7 @@ describe('Form component', () => {
370
347
  });
371
348
  it('supports navigating to Add New option with arrow keys and opens modal', async () => {
372
349
  const user = userEvent.setup();
373
- render(React.createElement(MemoryRouter, null,
374
- React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } })));
350
+ render(React.createElement(FormRenderer, { form: UpdateAccessibilityFormOne, onChange: () => { } }));
375
351
  await waitFor(() => {
376
352
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
377
353
  });
@@ -389,4 +365,660 @@ describe('Form component', () => {
389
365
  });
390
366
  });
391
367
  });
368
+ describe('when passed a related object entry', () => {
369
+ const setupTestMocks = (object, form, instances) => {
370
+ 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
+ };
372
+ describe('when in table view', () => {
373
+ describe('when mode is existing records only', () => {
374
+ const form = {
375
+ id: 'relatedObjectTestForm',
376
+ name: 'Related Object Test Form',
377
+ entries: [
378
+ {
379
+ type: 'input',
380
+ parameterId: 'specialtyType',
381
+ display: {
382
+ label: 'Speciality Type',
383
+ mode: 'existingOnly',
384
+ },
385
+ },
386
+ ],
387
+ actionId: '_update',
388
+ objectId: 'relatedObjectTestForm',
389
+ };
390
+ beforeEach(() => {
391
+ const relatedObjectTestFormObject = {
392
+ id: 'relatedObjectTestForm',
393
+ name: 'Related Object Test Form',
394
+ actions: [
395
+ {
396
+ id: '_update',
397
+ name: 'Update',
398
+ type: 'update',
399
+ parameters: [
400
+ {
401
+ id: 'specialtyType',
402
+ name: 'Related Object',
403
+ type: 'object',
404
+ objectId: 'specialtyType',
405
+ },
406
+ ],
407
+ outputEvent: 'updated',
408
+ },
409
+ ],
410
+ properties: [
411
+ {
412
+ id: 'specialtyType',
413
+ name: 'Related Object',
414
+ type: 'object',
415
+ },
416
+ ],
417
+ };
418
+ setupTestMocks(relatedObjectTestFormObject, form);
419
+ const specialtyTypeObject = {
420
+ id: 'specialtyType',
421
+ name: 'Specialty Type',
422
+ actions: [
423
+ {
424
+ id: '_create',
425
+ name: 'Create',
426
+ type: 'create',
427
+ parameters: [],
428
+ outputEvent: 'created',
429
+ defaultFormId: 'specialtyType',
430
+ },
431
+ {
432
+ id: '_update',
433
+ name: 'Update',
434
+ type: 'update',
435
+ parameters: [],
436
+ outputEvent: 'updated',
437
+ },
438
+ ],
439
+ properties: [],
440
+ };
441
+ const specialtyTypeForm = {
442
+ id: 'specialtyTypeForm',
443
+ name: 'Specialty Type Form',
444
+ entries: [],
445
+ actionId: '_update',
446
+ objectId: 'specialtyType',
447
+ };
448
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm);
449
+ });
450
+ it('does not display radio buttons if mode is existing records only', async () => {
451
+ const user = userEvent.setup();
452
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
453
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
454
+ // Make sure dialog loads
455
+ await screen.findByRole('dialog');
456
+ expect(screen.queryByRole('radiogroup', { name: 'Relation Type' })).not.toBeInTheDocument();
457
+ });
458
+ });
459
+ describe('when object has no searchable properties', () => {
460
+ const form = {
461
+ id: 'relatedObjectTestForm',
462
+ name: 'Related Object Test Form',
463
+ entries: [
464
+ {
465
+ type: 'input',
466
+ parameterId: 'specialtyType',
467
+ display: {
468
+ label: 'Speciality Type',
469
+ },
470
+ },
471
+ ],
472
+ actionId: '_update',
473
+ objectId: 'relatedObjectTestForm',
474
+ };
475
+ beforeEach(async () => {
476
+ const relatedObjectTestFormObject = {
477
+ id: 'relatedObjectTestForm',
478
+ name: 'Related Object Test Form',
479
+ actions: [
480
+ {
481
+ id: '_update',
482
+ name: 'Update',
483
+ type: 'update',
484
+ parameters: [
485
+ {
486
+ id: 'specialtyType',
487
+ name: 'Related Object',
488
+ type: 'object',
489
+ objectId: 'specialtyType',
490
+ },
491
+ ],
492
+ outputEvent: 'updated',
493
+ },
494
+ ],
495
+ properties: [
496
+ {
497
+ id: 'specialtyType',
498
+ name: 'Related Object',
499
+ type: 'object',
500
+ },
501
+ ],
502
+ };
503
+ setupTestMocks(relatedObjectTestFormObject, form);
504
+ const specialtyTypeObject = {
505
+ id: 'specialtyType',
506
+ name: 'Specialty Type',
507
+ actions: [
508
+ {
509
+ id: '_create',
510
+ name: 'Create',
511
+ type: 'create',
512
+ parameters: [],
513
+ outputEvent: 'created',
514
+ defaultFormId: 'specialtyType',
515
+ },
516
+ {
517
+ id: '_update',
518
+ name: 'Update',
519
+ type: 'update',
520
+ parameters: [],
521
+ outputEvent: 'updated',
522
+ },
523
+ ],
524
+ properties: [],
525
+ };
526
+ const specialtyTypeForm = {
527
+ id: 'specialtyTypeForm',
528
+ name: 'Specialty Type Form',
529
+ entries: [],
530
+ actionId: '_update',
531
+ objectId: 'specialtyType',
532
+ };
533
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm);
534
+ });
535
+ it("displays an error message if there aren't any searchable properties on a related object", async () => {
536
+ const user = userEvent.setup();
537
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
538
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
539
+ await screen.findByRole('dialog');
540
+ await waitFor(() => {
541
+ expect(screen.getByText('There are no searchable properties configured for this object')).toBeInTheDocument();
542
+ });
543
+ });
544
+ });
545
+ describe('when object has searchable properties', () => {
546
+ const form = {
547
+ id: 'relatedObjectTestForm',
548
+ name: 'Related Object Test Form',
549
+ entries: [
550
+ {
551
+ type: 'input',
552
+ parameterId: 'specialtyType',
553
+ display: {
554
+ label: 'Speciality Type',
555
+ relatedObjectDisplay: 'dialogBox',
556
+ mode: 'existingOnly',
557
+ },
558
+ },
559
+ ],
560
+ actionId: '_update',
561
+ objectId: 'relatedObjectTestForm',
562
+ };
563
+ beforeEach(async () => {
564
+ const relatedObjectTestFormObject = {
565
+ id: 'relatedObjectTestForm',
566
+ name: 'Related Object Test Form',
567
+ actions: [
568
+ {
569
+ id: '_update',
570
+ name: 'Update',
571
+ type: 'update',
572
+ parameters: [
573
+ {
574
+ id: 'specialtyType',
575
+ name: 'Related Object',
576
+ type: 'object',
577
+ objectId: 'specialtyType',
578
+ },
579
+ ],
580
+ outputEvent: 'updated',
581
+ },
582
+ ],
583
+ properties: [
584
+ {
585
+ id: 'specialtyType',
586
+ name: 'Related Object',
587
+ type: 'object',
588
+ },
589
+ ],
590
+ };
591
+ setupTestMocks(relatedObjectTestFormObject, form);
592
+ const specialtyTypeObject = {
593
+ id: 'specialtyType',
594
+ name: 'Specialty Type',
595
+ actions: [
596
+ {
597
+ id: '_create',
598
+ name: 'Create',
599
+ type: 'create',
600
+ parameters: [],
601
+ outputEvent: 'created',
602
+ defaultFormId: 'specialtyType',
603
+ },
604
+ ],
605
+ properties: [
606
+ {
607
+ id: 'name',
608
+ name: 'Name',
609
+ type: 'string',
610
+ searchable: true,
611
+ },
612
+ ],
613
+ };
614
+ const specialtyTypeForm = {
615
+ id: 'specialtyTypeForm',
616
+ name: 'Specialty Type Form',
617
+ entries: [
618
+ {
619
+ type: 'input',
620
+ parameterId: 'specialtyType',
621
+ display: {
622
+ label: 'Speciality Type',
623
+ relatedObjectDisplay: 'dialogBox',
624
+ mode: 'existingOnly',
625
+ },
626
+ },
627
+ ],
628
+ actionId: '_update',
629
+ objectId: 'specialtyType',
630
+ };
631
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm);
632
+ });
633
+ it('displays message to refine search if no search results are found', async () => {
634
+ const user = userEvent.setup();
635
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
636
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
637
+ await screen.findByRole('dialog');
638
+ const searchField = screen.getByRole('textbox', { name: 'Search' });
639
+ await user.type(searchField, 'Nonexistent Instance');
640
+ await screen.findByText(/refine your search/i);
641
+ });
642
+ });
643
+ describe('when mode is default (allows both new and existing)', () => {
644
+ const form = {
645
+ id: 'relatedObjectTestForm',
646
+ name: 'Related Object Test Form',
647
+ entries: [
648
+ {
649
+ type: 'input',
650
+ parameterId: 'specialtyType',
651
+ display: {
652
+ label: 'Speciality Type',
653
+ },
654
+ },
655
+ ],
656
+ actionId: '_update',
657
+ objectId: 'relatedObjectTestForm',
658
+ };
659
+ beforeEach(async () => {
660
+ const relatedObjectTestFormObject = {
661
+ id: 'relatedObjectTestForm',
662
+ name: 'Related Object Test Form',
663
+ actions: [
664
+ {
665
+ id: '_update',
666
+ name: 'Update',
667
+ type: 'update',
668
+ parameters: [
669
+ {
670
+ id: 'specialtyType',
671
+ name: 'Related Object',
672
+ type: 'object',
673
+ objectId: 'specialtyType',
674
+ },
675
+ ],
676
+ outputEvent: 'updated',
677
+ },
678
+ ],
679
+ properties: [
680
+ {
681
+ id: 'specialtyType',
682
+ name: 'Related Object',
683
+ type: 'object',
684
+ },
685
+ ],
686
+ };
687
+ setupTestMocks(relatedObjectTestFormObject, form);
688
+ const specialtyTypeForm = {
689
+ id: 'specialtyTypeForm',
690
+ name: 'Specialty Type Form',
691
+ entries: [
692
+ {
693
+ type: 'content',
694
+ html: '<div>Specialty Type Form Content</div>',
695
+ },
696
+ ],
697
+ actionId: '_create',
698
+ objectId: 'specialtyType',
699
+ };
700
+ const specialtyTypeObject = {
701
+ id: 'specialtyType',
702
+ name: 'Specialty Type',
703
+ actions: [
704
+ {
705
+ id: '_create',
706
+ name: 'Create',
707
+ type: 'create',
708
+ parameters: [],
709
+ outputEvent: 'created',
710
+ defaultFormId: 'specialtyTypeForm',
711
+ },
712
+ {
713
+ id: '_update',
714
+ name: 'Update',
715
+ type: 'update',
716
+ parameters: [],
717
+ outputEvent: 'updated',
718
+ },
719
+ ],
720
+ properties: [],
721
+ };
722
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm);
723
+ });
724
+ it('displays an add button for related object fields', async () => {
725
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
726
+ await screen.findByRole('button', { name: 'Add' });
727
+ });
728
+ it('shows a close icon in instance lookup mode', async () => {
729
+ const user = userEvent.setup();
730
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
731
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
732
+ await screen.findByRole('button', { name: 'Close' });
733
+ });
734
+ it('allows users to close modal using close icon in instance lookup mode', async () => {
735
+ const user = userEvent.setup();
736
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
737
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
738
+ const dialog = await screen.findByRole('dialog');
739
+ await user.click(await screen.findByRole('button', { name: 'Close' }));
740
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
741
+ });
742
+ it('shows a cancel button in instance lookup mode', async () => {
743
+ const user = userEvent.setup();
744
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
745
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
746
+ await screen.findByRole('button', { name: 'Cancel' });
747
+ });
748
+ it('closes dialog in instance lookup mode when clicking cancel', async () => {
749
+ const user = userEvent.setup();
750
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
751
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
752
+ const dialog = await screen.findByRole('dialog');
753
+ await user.click(await screen.findByRole('button', { name: 'Cancel' }));
754
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
755
+ });
756
+ it('displays radio buttons for new or existing record selection', async () => {
757
+ const user = userEvent.setup();
758
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
759
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
760
+ await screen.findByRole('radiogroup', { name: 'Relation Type' });
761
+ });
762
+ it('closes dialog if cancel button is clicked when creating a new record', async () => {
763
+ const user = userEvent.setup();
764
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
765
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
766
+ const dialog = await screen.findByRole('dialog');
767
+ await screen.findByRole('radiogroup', { name: 'Relation Type' });
768
+ const existingRecordButton = await screen.findByRole('radio', { name: /existing/i });
769
+ expect(existingRecordButton).toBeChecked();
770
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
771
+ await user.click(newRecordButton);
772
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
773
+ await user.click(cancelButton);
774
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
775
+ });
776
+ it('displays form if user switches to creating a new record', async () => {
777
+ const user = userEvent.setup();
778
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
779
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
780
+ await screen.findByRole('radiogroup', { name: 'Relation Type' });
781
+ const existingRecordButton = await screen.findByRole('radio', { name: /existing/i });
782
+ expect(existingRecordButton).toBeChecked();
783
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
784
+ await user.click(newRecordButton);
785
+ await screen.findByText('Specialty Type Form Content');
786
+ });
787
+ it('displays a close icon in record creation mode', async () => {
788
+ const user = userEvent.setup();
789
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
790
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
791
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
792
+ await user.click(newRecordButton);
793
+ await screen.findByText('Specialty Type Form Content');
794
+ await screen.findByRole('button', { name: 'Close' });
795
+ });
796
+ it('displays a cancel button in record creation mode', async () => {
797
+ const user = userEvent.setup();
798
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
799
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
800
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
801
+ await user.click(newRecordButton);
802
+ await screen.findByText('Specialty Type Form Content');
803
+ await screen.findByRole('button', { name: 'Cancel' });
804
+ });
805
+ it('displays a not found error in record creation mode if a form could not be found', async () => {
806
+ const user = userEvent.setup();
807
+ server.use(http.get('/api/data/forms/specialtyTypeForm', () => {
808
+ return HttpResponse.json({ error: 'Not found' }, { status: 404 });
809
+ }));
810
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
811
+ await user.click(await screen.findByRole('button', { name: 'Add' }));
812
+ const newRecordButton = await screen.findByRole('radio', { name: /new/i });
813
+ await user.click(newRecordButton);
814
+ await screen.findByText(/not found/i);
815
+ });
816
+ });
817
+ });
818
+ describe('when in dropdown view', () => {
819
+ describe('when mode is existing records only', () => {
820
+ const form = {
821
+ id: 'relatedObjectTestForm',
822
+ name: 'Related Object Test Form',
823
+ entries: [
824
+ {
825
+ type: 'input',
826
+ parameterId: 'specialtyType',
827
+ display: {
828
+ label: 'Speciality Type',
829
+ relatedObjectDisplay: 'dropdown',
830
+ mode: 'existingOnly',
831
+ },
832
+ },
833
+ ],
834
+ actionId: '_update',
835
+ objectId: 'relatedObjectTestForm',
836
+ };
837
+ beforeEach(() => {
838
+ const relatedObjectTestFormObject = {
839
+ id: 'relatedObjectTestForm',
840
+ name: 'Related Object Test Form',
841
+ actions: [
842
+ {
843
+ id: '_update',
844
+ name: 'Update',
845
+ type: 'update',
846
+ parameters: [
847
+ {
848
+ id: 'specialtyType',
849
+ name: 'Related Object',
850
+ type: 'object',
851
+ objectId: 'specialtyType',
852
+ },
853
+ ],
854
+ outputEvent: 'updated',
855
+ },
856
+ ],
857
+ properties: [
858
+ {
859
+ id: 'specialtyType',
860
+ name: 'Related Object',
861
+ type: 'object',
862
+ },
863
+ ],
864
+ };
865
+ setupTestMocks(relatedObjectTestFormObject, form);
866
+ const specialtyTypeObject = {
867
+ id: 'specialtyType',
868
+ name: 'Specialty Type',
869
+ actions: [
870
+ {
871
+ id: '_create',
872
+ name: 'Create',
873
+ type: 'create',
874
+ parameters: [],
875
+ outputEvent: 'created',
876
+ defaultFormId: 'specialtyType',
877
+ },
878
+ ],
879
+ properties: [],
880
+ };
881
+ const specialtyTypeForm = {
882
+ id: 'specialtyTypeForm',
883
+ name: 'Specialty Type Form',
884
+ entries: [
885
+ {
886
+ type: 'content',
887
+ html: '<div>Specialty Type Form Content</div>',
888
+ },
889
+ ],
890
+ actionId: '_update',
891
+ objectId: 'specialtyType',
892
+ };
893
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm, [
894
+ {
895
+ id: 'rnSpecialtyType1',
896
+ name: 'RN Specialty Type #1',
897
+ objectId: 'specialtyType',
898
+ },
899
+ ]);
900
+ });
901
+ it('does not provide option to add new object instances if mode is set to existing only', async () => {
902
+ const user = userEvent.setup();
903
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
904
+ // Navigate to and open dropdown
905
+ const dropdown = await screen.findByRole('combobox', { name: 'Speciality Type' });
906
+ await user.click(dropdown);
907
+ await screen.findByRole('listbox');
908
+ // Verify that the existing option is present
909
+ await screen.findByRole('option', { name: 'RN Specialty Type #1' });
910
+ // Verify that the "Add New" option is not present
911
+ expect(screen.queryByText('+ Add New')).not.toBeInTheDocument();
912
+ });
913
+ });
914
+ describe('when mode is default (allows both new and existing)', () => {
915
+ const form = {
916
+ id: 'relatedObjectTestForm',
917
+ name: 'Related Object Test Form',
918
+ entries: [
919
+ {
920
+ type: 'input',
921
+ parameterId: 'specialtyType',
922
+ display: {
923
+ label: 'Speciality Type',
924
+ relatedObjectDisplay: 'dropdown',
925
+ },
926
+ },
927
+ ],
928
+ actionId: '_update',
929
+ objectId: 'relatedObjectTestForm',
930
+ };
931
+ beforeEach(() => {
932
+ const relatedObjectTestFormObject = {
933
+ id: 'relatedObjectTestForm',
934
+ name: 'Related Object Test Form',
935
+ actions: [
936
+ {
937
+ id: '_update',
938
+ name: 'Update',
939
+ type: 'update',
940
+ parameters: [
941
+ {
942
+ id: 'specialtyType',
943
+ name: 'Related Object',
944
+ type: 'object',
945
+ objectId: 'specialtyType',
946
+ },
947
+ ],
948
+ outputEvent: 'updated',
949
+ },
950
+ ],
951
+ properties: [
952
+ {
953
+ id: 'specialtyType',
954
+ name: 'Related Object',
955
+ type: 'object',
956
+ },
957
+ ],
958
+ };
959
+ setupTestMocks(relatedObjectTestFormObject, form);
960
+ const specialtyTypeObject = {
961
+ id: 'specialtyType',
962
+ name: 'Specialty Type',
963
+ actions: [
964
+ {
965
+ id: '_create',
966
+ name: 'Create',
967
+ type: 'create',
968
+ parameters: [],
969
+ outputEvent: 'created',
970
+ defaultFormId: 'specialtyTypeForm',
971
+ },
972
+ ],
973
+ properties: [],
974
+ };
975
+ const specialtyTypeForm = {
976
+ id: 'specialtyTypeForm',
977
+ name: 'Specialty Type Form',
978
+ entries: [
979
+ {
980
+ type: 'content',
981
+ html: '<div>Specialty Type Form Content</div>',
982
+ },
983
+ ],
984
+ actionId: '_create',
985
+ objectId: 'specialtyType',
986
+ };
987
+ setupTestMocks(specialtyTypeObject, specialtyTypeForm, [
988
+ {
989
+ id: 'rnSpecialtyType1',
990
+ name: 'RN Specialty Type #1',
991
+ objectId: 'specialtyType',
992
+ },
993
+ ]);
994
+ });
995
+ it('provides option to add new object instances', async () => {
996
+ const user = userEvent.setup();
997
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
998
+ // Navigate to and open dropdown
999
+ const dropdown = await screen.findByRole('combobox', { name: 'Speciality Type' });
1000
+ await user.click(dropdown);
1001
+ await waitFor(() => {
1002
+ expect(screen.getByText('+ Add New')).toBeInTheDocument();
1003
+ });
1004
+ await user.click(screen.getByText('+ Add New'));
1005
+ await screen.findByRole('dialog');
1006
+ await screen.findByText('Specialty Type Form Content');
1007
+ });
1008
+ it('allows related object instance selection', async () => {
1009
+ const user = userEvent.setup();
1010
+ render(React.createElement(FormRenderer, { form: form, onChange: () => { } }));
1011
+ // Navigate to and open dropdown
1012
+ const dropdown = await screen.findByRole('combobox', { name: 'Speciality Type' });
1013
+ await user.click(dropdown);
1014
+ const option = await screen.findByRole('option', { name: 'RN Specialty Type #1' });
1015
+ await user.click(option);
1016
+ // Verify option has been removed from the document
1017
+ expect(option).not.toBeInTheDocument();
1018
+ // Verify selection
1019
+ screen.getByText('RN Specialty Type #1');
1020
+ });
1021
+ });
1022
+ });
1023
+ });
392
1024
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.10.0-dev.0",
3
+ "version": "1.10.0-testing.1",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",