@evoke-platform/ui-components 1.10.1-dev.1 → 1.10.1-dev.2
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,12 +2,14 @@ import { createFilterOptions, List, ListSubheader } from '@mui/material';
|
|
|
2
2
|
import { uniq } from 'lodash';
|
|
3
3
|
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Clear } from '../../../../icons';
|
|
5
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
5
6
|
import { Autocomplete, FormControl, FormControlLabel, IconButton, Radio, RadioGroup, TextField, Typography, } from '../../../core';
|
|
6
7
|
import { Box } from '../../../layout';
|
|
7
8
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
8
9
|
const filter = createFilterOptions();
|
|
9
10
|
const Select = (props) => {
|
|
10
11
|
const { id, property, defaultValue, error, errorMessage, onBlur, onChange, readOnly, isCombobox, selectOptions, required, size, isOptionEqualToValue, renderOption, getOptionLabel, disableCloseOnSelect, additionalProps, displayOption, sortBy, } = props;
|
|
12
|
+
const { onAutosave } = useFormContext();
|
|
11
13
|
const otherInputRef = useRef(null);
|
|
12
14
|
const [isOther, setIsOther] = useState(!!isCombobox &&
|
|
13
15
|
!!defaultValue &&
|
|
@@ -32,7 +34,7 @@ const Select = (props) => {
|
|
|
32
34
|
setValue(defaultValue);
|
|
33
35
|
}, [defaultValue]);
|
|
34
36
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
const handleChange = (event, selected) => {
|
|
37
|
+
const handleChange = async (event, selected) => {
|
|
36
38
|
if (Array.isArray(selected)) {
|
|
37
39
|
const newValues = selected.map((option) => option.value ?? option);
|
|
38
40
|
setValue(uniq(newValues));
|
|
@@ -56,6 +58,12 @@ const Select = (props) => {
|
|
|
56
58
|
onChange && onChange(property.id, selected, property);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
try {
|
|
62
|
+
await onAutosave?.(id);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error('Autosave failed:', error);
|
|
66
|
+
}
|
|
59
67
|
};
|
|
60
68
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
69
|
const handleInputValueChange = (event, selectValue) => {
|
|
@@ -121,22 +129,29 @@ const Select = (props) => {
|
|
|
121
129
|
}
|
|
122
130
|
setIsOtherFocused(false);
|
|
123
131
|
} })))))),
|
|
124
|
-
|
|
132
|
+
onChange && !readOnly && value && (React.createElement(Box, { sx: {
|
|
125
133
|
':hover': { cursor: 'pointer' },
|
|
126
134
|
marginTop: '4px',
|
|
127
135
|
display: 'flex',
|
|
128
136
|
alignItems: 'center',
|
|
129
137
|
} },
|
|
130
|
-
React.createElement(IconButton, { "aria-label": `Clear`, onClick: () => {
|
|
138
|
+
React.createElement(IconButton, { "aria-label": `Clear`, onClick: async () => {
|
|
131
139
|
setValue('');
|
|
132
140
|
property && onChange(property.id, '');
|
|
133
141
|
setIsOther(false);
|
|
142
|
+
// Trigger autosave immediately when clearing radio selection
|
|
143
|
+
try {
|
|
144
|
+
await onAutosave?.(id);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error('Autosave failed:', error);
|
|
148
|
+
}
|
|
134
149
|
}, sx: { padding: '3px', marginRight: '6px' } },
|
|
135
150
|
React.createElement(Clear, { sx: {
|
|
136
151
|
color: '#637381',
|
|
137
152
|
fontSize: '1.2rem',
|
|
138
153
|
} })),
|
|
139
|
-
React.createElement(Typography, { variant: "caption" }, "Clear Selection"))))) : (React.createElement(Autocomplete, { multiple: property?.type === 'array'
|
|
154
|
+
React.createElement(Typography, { variant: "caption" }, "Clear Selection"))))) : (React.createElement(Autocomplete, { multiple: property?.type === 'array', id: id, sortBy: sortBy, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, fullWidth: true, onBlur: onBlur, inputProps: {
|
|
140
155
|
...params.inputProps,
|
|
141
156
|
'aria-describedby': isCombobox ? `${id}-instructions` : undefined,
|
|
142
157
|
} })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
|
|
@@ -169,6 +184,10 @@ const Select = (props) => {
|
|
|
169
184
|
'& button.MuiButtonBase-root': {
|
|
170
185
|
visibility: 'visible',
|
|
171
186
|
},
|
|
187
|
+
}, slotProps: {
|
|
188
|
+
clearIndicator: {
|
|
189
|
+
'data-testid': 'autocomplete-clear-button',
|
|
190
|
+
},
|
|
172
191
|
}, forcePopupIcon: true, ...(isCombobox ? { selectOnFocus: true, handleHomeEndKeys: true, freeSolo: true } : {}), ...(additionalProps ?? {}) }));
|
|
173
192
|
};
|
|
174
193
|
export default Select;
|
|
@@ -326,12 +326,7 @@ describe('FormRendererContainer', () => {
|
|
|
326
326
|
await user.clear(cityField);
|
|
327
327
|
await user.type(cityField, 'Cambridge');
|
|
328
328
|
await user.tab(); // Blur the field
|
|
329
|
-
|
|
330
|
-
await waitFor(() => {
|
|
331
|
-
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
332
|
-
});
|
|
333
|
-
const lastCall = autosaveActionSpy.mock.lastCall?.[0];
|
|
334
|
-
expect(lastCall).toEqual(expect.objectContaining({
|
|
329
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
335
330
|
input: expect.objectContaining({
|
|
336
331
|
address: expect.objectContaining({
|
|
337
332
|
city: 'Cambridge',
|
|
@@ -448,15 +443,7 @@ describe('FormRendererContainer', () => {
|
|
|
448
443
|
// Wait for and select the autocomplete option
|
|
449
444
|
const autocompleteOption = await screen.findByText('456 Oak Street');
|
|
450
445
|
await user.click(autocompleteOption);
|
|
451
|
-
|
|
452
|
-
await waitFor(() => {
|
|
453
|
-
expect(autosaveActionSpy).toHaveBeenCalled();
|
|
454
|
-
});
|
|
455
|
-
// The autosave is triggered twice when selecting the autocomplete option,
|
|
456
|
-
// once by the selection and once by the onBlur event. We want to verify the last call
|
|
457
|
-
// has the correct data.
|
|
458
|
-
const lastCall = autosaveActionSpy.mock.lastCall?.[0];
|
|
459
|
-
expect(lastCall).toEqual(expect.objectContaining({
|
|
446
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
460
447
|
input: expect.objectContaining({
|
|
461
448
|
address: expect.objectContaining({
|
|
462
449
|
line1: '456 Oak Street',
|
|
@@ -531,6 +518,484 @@ describe('FormRendererContainer', () => {
|
|
|
531
518
|
// Verify autosave was not triggered
|
|
532
519
|
expect(autosaveActionSpy).not.toHaveBeenCalled();
|
|
533
520
|
});
|
|
521
|
+
it('should trigger autosave immediately when a radio button is selected', async () => {
|
|
522
|
+
const user = userEvent.setup();
|
|
523
|
+
const autosaveActionSpy = vi.fn();
|
|
524
|
+
// Create a form with a status field displayed as radio buttons
|
|
525
|
+
const licenseFormWithRadioButtons = {
|
|
526
|
+
id: 'licenseForm',
|
|
527
|
+
name: 'License Form',
|
|
528
|
+
objectId: 'license',
|
|
529
|
+
actionId: '_update',
|
|
530
|
+
autosaveActionId: '_autosave',
|
|
531
|
+
entries: [
|
|
532
|
+
{
|
|
533
|
+
parameterId: 'name',
|
|
534
|
+
type: 'input',
|
|
535
|
+
display: {
|
|
536
|
+
label: 'License Number',
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
parameterId: 'status',
|
|
541
|
+
type: 'input',
|
|
542
|
+
display: {
|
|
543
|
+
label: 'Status',
|
|
544
|
+
choicesDisplay: {
|
|
545
|
+
type: 'radioButton',
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
};
|
|
551
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => {
|
|
552
|
+
return HttpResponse.json({
|
|
553
|
+
id: 'test-license',
|
|
554
|
+
name: 'RN-123456',
|
|
555
|
+
status: 'Active',
|
|
556
|
+
});
|
|
557
|
+
}), http.get('/api/data/objects/license/instances/test-license/object', () => {
|
|
558
|
+
return HttpResponse.json(licenseObject);
|
|
559
|
+
}), http.get('/api/data/forms/licenseForm', () => {
|
|
560
|
+
return HttpResponse.json(licenseFormWithRadioButtons);
|
|
561
|
+
}), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
562
|
+
const body = (await request.json());
|
|
563
|
+
autosaveActionSpy(body);
|
|
564
|
+
return HttpResponse.json({
|
|
565
|
+
id: 'test-license',
|
|
566
|
+
name: 'RN-123456',
|
|
567
|
+
status: body.input.status,
|
|
568
|
+
});
|
|
569
|
+
}));
|
|
570
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
571
|
+
// Wait for the form to load and the radio buttons to appear
|
|
572
|
+
const inactiveRadio = await screen.findByRole('radio', { name: 'Inactive' });
|
|
573
|
+
// Click the "Inactive" radio button
|
|
574
|
+
await user.click(inactiveRadio);
|
|
575
|
+
// Verify autosave was triggered immediately with the new status
|
|
576
|
+
await waitFor(() => {
|
|
577
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
578
|
+
actionId: '_autosave',
|
|
579
|
+
input: expect.objectContaining({
|
|
580
|
+
status: 'Inactive',
|
|
581
|
+
}),
|
|
582
|
+
}));
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
it('should trigger autosave when the selection is cleared', async () => {
|
|
586
|
+
const user = userEvent.setup();
|
|
587
|
+
const autosaveActionSpy = vi.fn();
|
|
588
|
+
const licenseFormWithRadioButtons = {
|
|
589
|
+
id: 'licenseForm',
|
|
590
|
+
name: 'License Form',
|
|
591
|
+
objectId: 'license',
|
|
592
|
+
actionId: '_update',
|
|
593
|
+
autosaveActionId: '_autosave',
|
|
594
|
+
entries: [
|
|
595
|
+
{
|
|
596
|
+
parameterId: 'name',
|
|
597
|
+
type: 'input',
|
|
598
|
+
display: {
|
|
599
|
+
label: 'License Number',
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
parameterId: 'status',
|
|
604
|
+
type: 'input',
|
|
605
|
+
display: {
|
|
606
|
+
label: 'Status',
|
|
607
|
+
choicesDisplay: {
|
|
608
|
+
type: 'radioButton',
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
],
|
|
613
|
+
};
|
|
614
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => {
|
|
615
|
+
return HttpResponse.json({
|
|
616
|
+
id: 'test-license',
|
|
617
|
+
name: 'RN-123456',
|
|
618
|
+
status: 'Active',
|
|
619
|
+
});
|
|
620
|
+
}), http.get('/api/data/objects/license/instances/test-license/object', () => {
|
|
621
|
+
return HttpResponse.json(licenseObject);
|
|
622
|
+
}), http.get('/api/data/forms/licenseForm', () => {
|
|
623
|
+
return HttpResponse.json(licenseFormWithRadioButtons);
|
|
624
|
+
}), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
625
|
+
const body = (await request.json());
|
|
626
|
+
autosaveActionSpy(body);
|
|
627
|
+
return HttpResponse.json({
|
|
628
|
+
id: 'test-license',
|
|
629
|
+
name: 'RN-123456',
|
|
630
|
+
status: body.input.status,
|
|
631
|
+
});
|
|
632
|
+
}));
|
|
633
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
634
|
+
const clearButton = await screen.findByRole('button', { name: 'Clear' });
|
|
635
|
+
await user.click(clearButton);
|
|
636
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
637
|
+
actionId: '_autosave',
|
|
638
|
+
input: expect.objectContaining({
|
|
639
|
+
status: null,
|
|
640
|
+
}),
|
|
641
|
+
}));
|
|
642
|
+
});
|
|
643
|
+
it('should trigger autosave immediately when a dropdown option is selected', async () => {
|
|
644
|
+
const user = userEvent.setup();
|
|
645
|
+
const autosaveActionSpy = vi.fn();
|
|
646
|
+
const licenseFormWithDropdown = {
|
|
647
|
+
id: 'licenseForm',
|
|
648
|
+
name: 'License Form',
|
|
649
|
+
objectId: 'license',
|
|
650
|
+
actionId: '_update',
|
|
651
|
+
autosaveActionId: '_autosave',
|
|
652
|
+
entries: [
|
|
653
|
+
{
|
|
654
|
+
parameterId: 'name',
|
|
655
|
+
type: 'input',
|
|
656
|
+
display: { label: 'License Number' },
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
parameterId: 'status',
|
|
660
|
+
type: 'input',
|
|
661
|
+
display: {
|
|
662
|
+
label: 'Status',
|
|
663
|
+
choicesDisplay: { type: 'dropdown' },
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
],
|
|
667
|
+
};
|
|
668
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json({ id: 'test-license', name: 'RN-123456', status: 'Active' })), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithDropdown)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
669
|
+
const body = (await request.json());
|
|
670
|
+
autosaveActionSpy(body);
|
|
671
|
+
return HttpResponse.json({ id: 'test-license', name: 'RN-123456', status: body.input.status });
|
|
672
|
+
}));
|
|
673
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
674
|
+
// Open the dropdown/combobox and choose an option
|
|
675
|
+
const combobox = await screen.findByRole('combobox', { name: 'Status' });
|
|
676
|
+
await user.click(combobox);
|
|
677
|
+
const inactiveOption = await screen.findByRole('option', { name: 'Inactive' });
|
|
678
|
+
await user.click(inactiveOption);
|
|
679
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
680
|
+
actionId: '_autosave',
|
|
681
|
+
input: expect.objectContaining({
|
|
682
|
+
status: 'Inactive',
|
|
683
|
+
}),
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
it('should trigger autosave when dropdown selection is cleared', async () => {
|
|
687
|
+
const user = userEvent.setup();
|
|
688
|
+
const autosaveActionSpy = vi.fn();
|
|
689
|
+
const licenseFormWithDropdown = {
|
|
690
|
+
id: 'licenseForm',
|
|
691
|
+
name: 'License Form',
|
|
692
|
+
objectId: 'license',
|
|
693
|
+
actionId: '_update',
|
|
694
|
+
autosaveActionId: '_autosave',
|
|
695
|
+
entries: [
|
|
696
|
+
{
|
|
697
|
+
parameterId: 'name',
|
|
698
|
+
type: 'input',
|
|
699
|
+
display: { label: 'License Number' },
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
parameterId: 'status',
|
|
703
|
+
type: 'input',
|
|
704
|
+
display: {
|
|
705
|
+
label: 'Status',
|
|
706
|
+
choicesDisplay: { type: 'dropdown' },
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
};
|
|
711
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json({ id: 'test-license', name: 'RN-123456', status: 'Active' })), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithDropdown)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
712
|
+
const body = (await request.json());
|
|
713
|
+
autosaveActionSpy(body);
|
|
714
|
+
return HttpResponse.json({ id: 'test-license', name: 'RN-123456', status: body.input.status });
|
|
715
|
+
}));
|
|
716
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
717
|
+
const combobox = await screen.findByRole('combobox', { name: 'Status' });
|
|
718
|
+
await user.click(combobox);
|
|
719
|
+
// Clear button has tabindex="-1" so find it via testid
|
|
720
|
+
const clearButton = await screen.findByTestId('autocomplete-clear-button');
|
|
721
|
+
await user.click(clearButton);
|
|
722
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
723
|
+
actionId: '_autosave',
|
|
724
|
+
input: expect.objectContaining({
|
|
725
|
+
status: null,
|
|
726
|
+
}),
|
|
727
|
+
}));
|
|
728
|
+
});
|
|
729
|
+
it('should trigger autosave for multi-select when selecting an option', async () => {
|
|
730
|
+
const user = userEvent.setup();
|
|
731
|
+
const autosaveActionSpy = vi.fn();
|
|
732
|
+
const licenseFormWithMultiSelect = {
|
|
733
|
+
id: 'licenseForm',
|
|
734
|
+
name: 'License Form',
|
|
735
|
+
objectId: 'license',
|
|
736
|
+
actionId: '_update',
|
|
737
|
+
autosaveActionId: '_autosave',
|
|
738
|
+
entries: [
|
|
739
|
+
{
|
|
740
|
+
parameterId: 'name',
|
|
741
|
+
type: 'input',
|
|
742
|
+
display: { label: 'License Number' },
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
type: 'inputField',
|
|
746
|
+
input: {
|
|
747
|
+
id: 'categories',
|
|
748
|
+
type: 'array',
|
|
749
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
750
|
+
},
|
|
751
|
+
display: {
|
|
752
|
+
label: 'Categories',
|
|
753
|
+
choicesDisplay: { type: 'dropdown' },
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
};
|
|
758
|
+
const licenseInstanceInitial = { id: 'test-license', name: 'RN-123456' };
|
|
759
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json(licenseInstanceInitial)), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithMultiSelect)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
760
|
+
const body = (await request.json());
|
|
761
|
+
autosaveActionSpy(body);
|
|
762
|
+
return HttpResponse.json({
|
|
763
|
+
id: 'test-license',
|
|
764
|
+
name: body.input.name ?? 'RN-123456',
|
|
765
|
+
categories: body.input.categories,
|
|
766
|
+
});
|
|
767
|
+
}));
|
|
768
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
769
|
+
// Open the multi-select combobox and choose an additional option
|
|
770
|
+
const combobox = await screen.findByRole('combobox', { name: 'Categories' });
|
|
771
|
+
await user.click(combobox);
|
|
772
|
+
const plumbingOption = await screen.findByRole('option', { name: 'Electrical' });
|
|
773
|
+
await user.click(plumbingOption);
|
|
774
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
775
|
+
actionId: '_autosave',
|
|
776
|
+
input: expect.objectContaining({ categories: ['Electrical'] }),
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
it('should trigger autosave for multi-select when adding an option', async () => {
|
|
780
|
+
const user = userEvent.setup();
|
|
781
|
+
const autosaveActionSpy = vi.fn();
|
|
782
|
+
const licenseFormWithMultiSelect = {
|
|
783
|
+
id: 'licenseForm',
|
|
784
|
+
name: 'License Form',
|
|
785
|
+
objectId: 'license',
|
|
786
|
+
actionId: '_update',
|
|
787
|
+
autosaveActionId: '_autosave',
|
|
788
|
+
entries: [
|
|
789
|
+
{
|
|
790
|
+
parameterId: 'name',
|
|
791
|
+
type: 'input',
|
|
792
|
+
display: { label: 'License Number' },
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
type: 'inputField',
|
|
796
|
+
input: {
|
|
797
|
+
id: 'categories',
|
|
798
|
+
type: 'array',
|
|
799
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
800
|
+
},
|
|
801
|
+
display: {
|
|
802
|
+
label: 'Categories',
|
|
803
|
+
choicesDisplay: { type: 'dropdown' },
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
};
|
|
808
|
+
const licenseInstanceInitial = { id: 'test-license', name: 'RN-123456', categories: ['Electrical'] };
|
|
809
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json(licenseInstanceInitial)), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithMultiSelect)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
810
|
+
const body = (await request.json());
|
|
811
|
+
autosaveActionSpy(body);
|
|
812
|
+
return HttpResponse.json({
|
|
813
|
+
id: 'test-license',
|
|
814
|
+
name: body.input.name ?? 'RN-123456',
|
|
815
|
+
categories: body.input.categories,
|
|
816
|
+
});
|
|
817
|
+
}));
|
|
818
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
819
|
+
// Open the multi-select combobox and choose an additional option
|
|
820
|
+
const combobox = await screen.findByRole('combobox', { name: 'Categories' });
|
|
821
|
+
await user.click(combobox);
|
|
822
|
+
const plumbingOption = await screen.findByRole('option', { name: 'Plumbing' });
|
|
823
|
+
await user.click(plumbingOption);
|
|
824
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
825
|
+
actionId: '_autosave',
|
|
826
|
+
input: expect.objectContaining({
|
|
827
|
+
categories: expect.arrayContaining(['Electrical', 'Plumbing']),
|
|
828
|
+
}),
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
it('should trigger autosave when a multi-select option is removed', async () => {
|
|
832
|
+
const user = userEvent.setup();
|
|
833
|
+
const autosaveActionSpy = vi.fn();
|
|
834
|
+
const licenseFormWithMultiSelect = {
|
|
835
|
+
id: 'licenseForm',
|
|
836
|
+
name: 'License Form',
|
|
837
|
+
objectId: 'license',
|
|
838
|
+
actionId: '_update',
|
|
839
|
+
autosaveActionId: '_autosave',
|
|
840
|
+
entries: [
|
|
841
|
+
{
|
|
842
|
+
parameterId: 'name',
|
|
843
|
+
type: 'input',
|
|
844
|
+
display: { label: 'License Number' },
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
type: 'inputField',
|
|
848
|
+
input: {
|
|
849
|
+
id: 'categories',
|
|
850
|
+
type: 'array',
|
|
851
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
852
|
+
},
|
|
853
|
+
display: {
|
|
854
|
+
label: 'Categories',
|
|
855
|
+
choicesDisplay: { type: 'dropdown' },
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
],
|
|
859
|
+
};
|
|
860
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json({
|
|
861
|
+
id: 'test-license',
|
|
862
|
+
name: 'RN-123456',
|
|
863
|
+
categories: ['Electrical', 'Plumbing'],
|
|
864
|
+
})), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithMultiSelect)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
865
|
+
const body = (await request.json());
|
|
866
|
+
autosaveActionSpy(body);
|
|
867
|
+
return HttpResponse.json({
|
|
868
|
+
id: 'test-license',
|
|
869
|
+
name: 'RN-123456',
|
|
870
|
+
categories: body.input.categories,
|
|
871
|
+
});
|
|
872
|
+
}));
|
|
873
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
874
|
+
const electricalChipLabel = await screen.findByRole('button', { name: 'Electrical' });
|
|
875
|
+
// Mui autoamtically provides a test id.
|
|
876
|
+
const deleteIcon = await within(electricalChipLabel).findByTestId('CancelIcon');
|
|
877
|
+
await user.click(deleteIcon);
|
|
878
|
+
// Autosave should be called with only Plumbing remaining
|
|
879
|
+
await waitFor(() => {
|
|
880
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
881
|
+
actionId: '_autosave',
|
|
882
|
+
input: expect.objectContaining({
|
|
883
|
+
categories: ['Plumbing'],
|
|
884
|
+
}),
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
it('should trigger autosave when a single multi-select option is removed via the dropdown', async () => {
|
|
889
|
+
const user = userEvent.setup();
|
|
890
|
+
const autosaveActionSpy = vi.fn();
|
|
891
|
+
const licenseFormWithMultiSelect = {
|
|
892
|
+
id: 'licenseForm',
|
|
893
|
+
name: 'License Form',
|
|
894
|
+
objectId: 'license',
|
|
895
|
+
actionId: '_update',
|
|
896
|
+
autosaveActionId: '_autosave',
|
|
897
|
+
entries: [
|
|
898
|
+
{
|
|
899
|
+
parameterId: 'name',
|
|
900
|
+
type: 'input',
|
|
901
|
+
display: { label: 'License Number' },
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
type: 'inputField',
|
|
905
|
+
input: {
|
|
906
|
+
id: 'categories',
|
|
907
|
+
type: 'array',
|
|
908
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
909
|
+
},
|
|
910
|
+
display: {
|
|
911
|
+
label: 'Categories',
|
|
912
|
+
choicesDisplay: { type: 'dropdown' },
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
],
|
|
916
|
+
};
|
|
917
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json({
|
|
918
|
+
id: 'test-license',
|
|
919
|
+
name: 'RN-123456',
|
|
920
|
+
categories: ['Electrical', 'Plumbing'],
|
|
921
|
+
})), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithMultiSelect)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
922
|
+
const body = (await request.json());
|
|
923
|
+
autosaveActionSpy(body);
|
|
924
|
+
return HttpResponse.json({
|
|
925
|
+
id: 'test-license',
|
|
926
|
+
name: 'RN-123456',
|
|
927
|
+
categories: body.input.categories,
|
|
928
|
+
});
|
|
929
|
+
}));
|
|
930
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
931
|
+
// Open the multi-select combobox and unselect the 'Plumbing' option
|
|
932
|
+
const combobox = await screen.findByRole('combobox', { name: 'Categories' });
|
|
933
|
+
await user.click(combobox);
|
|
934
|
+
// Click the option to toggle/unselect it (initially selected)
|
|
935
|
+
const plumbingOption = await screen.findByRole('option', { name: 'Plumbing' });
|
|
936
|
+
await user.click(plumbingOption);
|
|
937
|
+
// Expect autosave called with categories array no longer containing 'Plumbing'
|
|
938
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
939
|
+
actionId: '_autosave',
|
|
940
|
+
input: expect.objectContaining({ categories: ['Electrical'] }),
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
it('should trigger autosave when multi-select is cleared', async () => {
|
|
944
|
+
const user = userEvent.setup();
|
|
945
|
+
const autosaveActionSpy = vi.fn();
|
|
946
|
+
const licenseFormWithMultiSelect = {
|
|
947
|
+
id: 'licenseForm',
|
|
948
|
+
name: 'License Form',
|
|
949
|
+
objectId: 'license',
|
|
950
|
+
actionId: '_update',
|
|
951
|
+
autosaveActionId: '_autosave',
|
|
952
|
+
entries: [
|
|
953
|
+
{
|
|
954
|
+
parameterId: 'name',
|
|
955
|
+
type: 'input',
|
|
956
|
+
display: { label: 'License Number' },
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
type: 'inputField',
|
|
960
|
+
input: {
|
|
961
|
+
id: 'categories',
|
|
962
|
+
type: 'array',
|
|
963
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
964
|
+
},
|
|
965
|
+
display: {
|
|
966
|
+
label: 'Categories',
|
|
967
|
+
choicesDisplay: { type: 'dropdown' },
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
],
|
|
971
|
+
};
|
|
972
|
+
server.use(http.get('/api/data/objects/license/instances/test-license', () => HttpResponse.json({
|
|
973
|
+
id: 'test-license',
|
|
974
|
+
name: 'RN-123456',
|
|
975
|
+
categories: ['Electrical', 'Plumbing'],
|
|
976
|
+
})), http.get('/api/data/objects/license/instances/test-license/object', () => HttpResponse.json(licenseObject)), http.get('/api/data/forms/licenseForm', () => HttpResponse.json(licenseFormWithMultiSelect)), http.post('/api/data/objects/license/instances/test-license/actions', async ({ request }) => {
|
|
977
|
+
const body = (await request.json());
|
|
978
|
+
autosaveActionSpy(body);
|
|
979
|
+
return HttpResponse.json({
|
|
980
|
+
id: 'test-license',
|
|
981
|
+
name: 'RN-123456',
|
|
982
|
+
categories: body.input.categories,
|
|
983
|
+
});
|
|
984
|
+
}));
|
|
985
|
+
render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
|
|
986
|
+
// Open the multi-select combobox and click the clear button
|
|
987
|
+
const combobox = await screen.findByRole('combobox', { name: 'Categories' });
|
|
988
|
+
await user.click(combobox);
|
|
989
|
+
// Clear button has tabindex="-1" so find via testid
|
|
990
|
+
const clearButton = await screen.findByTestId('autocomplete-clear-button');
|
|
991
|
+
await user.click(clearButton);
|
|
992
|
+
expect(autosaveActionSpy).toHaveBeenCalledWith({
|
|
993
|
+
actionId: '_autosave',
|
|
994
|
+
input: expect.objectContaining({
|
|
995
|
+
categories: expect.arrayContaining([]),
|
|
996
|
+
}),
|
|
997
|
+
});
|
|
998
|
+
});
|
|
534
999
|
});
|
|
535
1000
|
it('should display a submit button', async () => {
|
|
536
1001
|
const form = {
|
|
@@ -160,6 +160,12 @@ export const licenseObject = {
|
|
|
160
160
|
type: 'string',
|
|
161
161
|
enum: ['Active', 'Inactive'],
|
|
162
162
|
},
|
|
163
|
+
{
|
|
164
|
+
id: 'categories',
|
|
165
|
+
name: 'Categories',
|
|
166
|
+
type: 'array',
|
|
167
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
168
|
+
},
|
|
163
169
|
{
|
|
164
170
|
id: 'licenseType',
|
|
165
171
|
name: 'License Type',
|
|
@@ -188,6 +194,13 @@ export const licenseObject = {
|
|
|
188
194
|
id: 'status',
|
|
189
195
|
name: 'Status',
|
|
190
196
|
type: 'string',
|
|
197
|
+
enum: ['Active', 'Inactive'],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'categories',
|
|
201
|
+
name: 'Categories',
|
|
202
|
+
type: 'array',
|
|
203
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
191
204
|
},
|
|
192
205
|
{
|
|
193
206
|
id: 'address.line1',
|
|
@@ -236,6 +249,13 @@ export const licenseObject = {
|
|
|
236
249
|
id: 'status',
|
|
237
250
|
name: 'Status',
|
|
238
251
|
type: 'string',
|
|
252
|
+
enum: ['Active', 'Inactive'],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: 'categories',
|
|
256
|
+
name: 'Categories',
|
|
257
|
+
type: 'array',
|
|
258
|
+
enum: ['Electrical', 'Plumbing', 'Mechanical', 'Other'],
|
|
239
259
|
},
|
|
240
260
|
{
|
|
241
261
|
id: 'address.line1',
|