@evoke-platform/ui-components 1.11.0-dev.0 → 1.11.0-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.
- package/dist/published/components/custom/FormField/Select/Select.js +8 -1
- package/dist/published/components/custom/FormField/Select/Select.test.js +101 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +4 -4
- package/package.json +1 -1
|
@@ -118,7 +118,7 @@ const Select = (props) => {
|
|
|
118
118
|
setValue(e.target.value);
|
|
119
119
|
setInputValue(e.target.value);
|
|
120
120
|
onChange && onChange(property.id, e.target.value, property);
|
|
121
|
-
}, size: 'small', onFocus: () => setIsOtherFocused(true), onBlur: (e) => {
|
|
121
|
+
}, size: 'small', onFocus: () => setIsOtherFocused(true), onBlur: async (e) => {
|
|
122
122
|
// If another radio button is focused, change the value to that radio button's value.
|
|
123
123
|
const targetElement = e.nativeEvent.relatedTarget;
|
|
124
124
|
if (targetElement?.defaultValue && targetElement.name === `radioGroup-${id}`) {
|
|
@@ -128,6 +128,13 @@ const Select = (props) => {
|
|
|
128
128
|
onChange && onChange(property.id, e.target.value, property);
|
|
129
129
|
}
|
|
130
130
|
setIsOtherFocused(false);
|
|
131
|
+
// Trigger autosave when the custom value field loses focus
|
|
132
|
+
try {
|
|
133
|
+
await onAutosave?.(id);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error('Autosave failed:', error);
|
|
137
|
+
}
|
|
131
138
|
} })))))),
|
|
132
139
|
onChange && !readOnly && value && (React.createElement(Box, { sx: {
|
|
133
140
|
':hover': { cursor: 'pointer' },
|
|
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
|
|
|
2
2
|
import { userEvent } from '@testing-library/user-event';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { FormContext } from '../../FormV2/components/FormContext';
|
|
5
6
|
import Select from './Select';
|
|
6
7
|
describe('Single select', () => {
|
|
7
8
|
// Right now an object property is required for this to function, but eventually this should go
|
|
@@ -61,6 +62,27 @@ describe('Single select', () => {
|
|
|
61
62
|
await user.click(customOption);
|
|
62
63
|
expect(onChangeMock).toBeCalledWith('selectOptions', expect.objectContaining({ label: 'Add "custom option"', value: 'custom option' }), choiceProperty);
|
|
63
64
|
});
|
|
65
|
+
it('triggers autosave when entering a custom value in dropdown combobox', async () => {
|
|
66
|
+
const user = userEvent.setup();
|
|
67
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
|
68
|
+
const onAutosaveMock = vi.fn();
|
|
69
|
+
const options = ['option 1', 'option 2', 'option 3'];
|
|
70
|
+
render(React.createElement(FormContext.Provider, { value: {
|
|
71
|
+
fetchedOptions: {},
|
|
72
|
+
setFetchedOptions: () => { },
|
|
73
|
+
onAutosave: onAutosaveMock,
|
|
74
|
+
width: 1200,
|
|
75
|
+
} },
|
|
76
|
+
React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true })));
|
|
77
|
+
const input = screen.getByRole('combobox');
|
|
78
|
+
await user.click(input);
|
|
79
|
+
await user.type(input, 'custom option');
|
|
80
|
+
// Select the custom option from dropdown
|
|
81
|
+
const customOption = await screen.findByRole('option', { name: 'Add "custom option"' });
|
|
82
|
+
await user.click(customOption);
|
|
83
|
+
// Verify autosave was triggered
|
|
84
|
+
expect(onAutosaveMock).toHaveBeenCalledWith('testSelect');
|
|
85
|
+
});
|
|
64
86
|
});
|
|
65
87
|
describe('Multi select', () => {
|
|
66
88
|
// Right now an object property is required for this to function, but eventually this should go
|
|
@@ -122,6 +144,34 @@ describe('Multi select', () => {
|
|
|
122
144
|
expect(onChangeMock).toBeCalledTimes(2);
|
|
123
145
|
expect(onChangeMock).lastCalledWith('multiSelect', ['custom option 1', 'option 1'], multiChoiceProperty);
|
|
124
146
|
});
|
|
147
|
+
it('triggers autosave when entering custom values in multiselect combobox', async () => {
|
|
148
|
+
const user = userEvent.setup();
|
|
149
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
|
150
|
+
const onAutosaveMock = vi.fn();
|
|
151
|
+
const options = ['option 1', 'option 2', 'option 3'];
|
|
152
|
+
render(React.createElement(FormContext.Provider, { value: {
|
|
153
|
+
fetchedOptions: {},
|
|
154
|
+
setFetchedOptions: () => { },
|
|
155
|
+
onAutosave: onAutosaveMock,
|
|
156
|
+
width: 1200,
|
|
157
|
+
} },
|
|
158
|
+
React.createElement(Select, { id: "multiSelect", property: multiChoiceProperty, selectOptions: options, onChange: onChangeMock, isCombobox: true })));
|
|
159
|
+
const input = screen.getByRole('combobox');
|
|
160
|
+
await user.click(input);
|
|
161
|
+
await user.type(input, 'custom option 1');
|
|
162
|
+
// Select the first custom option
|
|
163
|
+
const customOption1 = await screen.findByRole('option', { name: 'Add "custom option 1"' });
|
|
164
|
+
await user.click(customOption1);
|
|
165
|
+
// Verify autosave was triggered for first custom value
|
|
166
|
+
expect(onAutosaveMock).toHaveBeenCalledWith('multiSelect');
|
|
167
|
+
onAutosaveMock.mockClear();
|
|
168
|
+
// Add a second custom option
|
|
169
|
+
await user.type(input, 'custom option 2');
|
|
170
|
+
const customOption2 = await screen.findByRole('option', { name: 'Add "custom option 2"' });
|
|
171
|
+
await user.click(customOption2);
|
|
172
|
+
// Verify autosave was triggered for second custom value
|
|
173
|
+
expect(onAutosaveMock).toHaveBeenCalledWith('multiSelect');
|
|
174
|
+
});
|
|
125
175
|
});
|
|
126
176
|
describe('Radio Single select', () => {
|
|
127
177
|
const choiceProperty = {
|
|
@@ -166,4 +216,55 @@ describe('Radio Single select', () => {
|
|
|
166
216
|
await user.type(otherTextField, 'custom option');
|
|
167
217
|
expect(onChangeMock).toBeCalledWith('selectOptions', expect.stringContaining('custom option'), choiceProperty);
|
|
168
218
|
});
|
|
219
|
+
it('triggers autosave when a custom value is entered', async () => {
|
|
220
|
+
const user = userEvent.setup();
|
|
221
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
|
222
|
+
const onAutosaveMock = vi.fn();
|
|
223
|
+
const options = ['option 1', 'option 2', 'option 3'];
|
|
224
|
+
render(React.createElement(FormContext.Provider, { value: {
|
|
225
|
+
fetchedOptions: {},
|
|
226
|
+
setFetchedOptions: () => { },
|
|
227
|
+
onAutosave: onAutosaveMock,
|
|
228
|
+
width: 1200,
|
|
229
|
+
} },
|
|
230
|
+
React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock, isCombobox: true })));
|
|
231
|
+
// Select the "Other" radio option
|
|
232
|
+
await user.click(screen.getByRole('radio', { name: 'Other' }));
|
|
233
|
+
const otherTextField = await screen.findByRole('textbox', { name: 'Other' });
|
|
234
|
+
// Type a custom value
|
|
235
|
+
await user.type(otherTextField, 'custom option');
|
|
236
|
+
// Blur the text field by clicking elsewhere
|
|
237
|
+
await user.tab();
|
|
238
|
+
// Verify autosave was triggered
|
|
239
|
+
expect(onAutosaveMock).toHaveBeenCalledWith('testSelect');
|
|
240
|
+
});
|
|
241
|
+
it('triggers autosave when clearing the custom value', async () => {
|
|
242
|
+
const user = userEvent.setup();
|
|
243
|
+
const onChangeMock = vi.fn((name, value, property) => { });
|
|
244
|
+
const onAutosaveMock = vi.fn();
|
|
245
|
+
const options = ['option 1', 'option 2', 'option 3'];
|
|
246
|
+
render(React.createElement(FormContext.Provider, { value: {
|
|
247
|
+
fetchedOptions: {},
|
|
248
|
+
setFetchedOptions: () => { },
|
|
249
|
+
onAutosave: onAutosaveMock,
|
|
250
|
+
width: 1200,
|
|
251
|
+
} },
|
|
252
|
+
React.createElement(Select, { id: "testSelect", property: choiceProperty, selectOptions: options, displayOption: 'radioButton', sortBy: 'ASC', onChange: onChangeMock, isCombobox: true })));
|
|
253
|
+
// Select the "Other" radio option and enter a custom value
|
|
254
|
+
await user.click(await screen.findByRole('radio', { name: 'Other' }));
|
|
255
|
+
const otherTextField = await screen.findByRole('textbox', { name: 'Other' });
|
|
256
|
+
await user.type(otherTextField, 'custom option');
|
|
257
|
+
// Blur to set the value and trigger the first autosave
|
|
258
|
+
await user.tab();
|
|
259
|
+
// Clear the mock to isolate the clear action
|
|
260
|
+
onAutosaveMock.mockClear();
|
|
261
|
+
onChangeMock.mockClear();
|
|
262
|
+
// Click the clear button (now visible since value is set)
|
|
263
|
+
const clearButton = await screen.findByRole('button', { name: 'Clear' });
|
|
264
|
+
await user.click(clearButton);
|
|
265
|
+
// Verify onChange was called with empty value
|
|
266
|
+
expect(onChangeMock).toHaveBeenCalledWith('selectOptions', '');
|
|
267
|
+
// Verify autosave was triggered after clearing
|
|
268
|
+
expect(onAutosaveMock).toHaveBeenCalledWith('testSelect');
|
|
269
|
+
});
|
|
169
270
|
});
|
|
@@ -11,7 +11,7 @@ import { getDefaultPages, getPrefixedUrl, transformToWhere } from '../../utils';
|
|
|
11
11
|
import RelatedObjectInstance from './RelatedObjectInstance';
|
|
12
12
|
const ObjectPropertyInput = (props) => {
|
|
13
13
|
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, relatedObjectId, } = props;
|
|
14
|
-
const { fetchedOptions, setFetchedOptions,
|
|
14
|
+
const { fetchedOptions, setFetchedOptions, fieldHeight, handleChange: handleChangeObjectField, onAutosave: onAutosave, instance, } = useFormContext();
|
|
15
15
|
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
16
16
|
const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
|
|
17
17
|
const [openCreateDialog, setOpenCreateDialog] = useState(false);
|
|
@@ -205,8 +205,8 @@ const ObjectPropertyInput = (props) => {
|
|
|
205
205
|
}, [relatedObjectId, fetchedOptions, id]);
|
|
206
206
|
useEffect(() => {
|
|
207
207
|
(async () => {
|
|
208
|
-
if (
|
|
209
|
-
const pages = await getDefaultPages(
|
|
208
|
+
if (fetchedOptions[`${id}NavigationSlug`] === undefined) {
|
|
209
|
+
const pages = await getDefaultPages([{ ...fieldDefinition, objectId: relatedObjectId }], defaultPages, findDefaultPageSlugFor);
|
|
210
210
|
if (relatedObjectId && pages[relatedObjectId]) {
|
|
211
211
|
setNavigationSlug(pages[relatedObjectId]);
|
|
212
212
|
setFetchedOptions({
|
|
@@ -221,7 +221,7 @@ const ObjectPropertyInput = (props) => {
|
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
})();
|
|
224
|
-
}, [
|
|
224
|
+
}, [id, fieldDefinition, defaultPages, findDefaultPageSlugFor, relatedObjectId, fetchedOptions]);
|
|
225
225
|
const handleClose = () => {
|
|
226
226
|
setOpenCreateDialog(false);
|
|
227
227
|
};
|