diff --git a/cypress/integration/edit.js b/cypress/integration/edit.js index 2db2e5eb259..8bcf93ac1e6 100644 --- a/cypress/integration/edit.js +++ b/cypress/integration/edit.js @@ -85,6 +85,8 @@ describe('Edit Page', () => { EditPostTagsPage.navigate(); EditPostTagsPage.gotoTab(3); + cy.wait(250); + // Music is selected by default cy.get( EditPostTagsPage.elements.input('tags', 'reference-array-input') diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx index e1078d72af4..e4090bbfbdc 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.spec.tsx @@ -924,4 +924,104 @@ describe('', () => { expect(queryByText('New Kid On The Block')).not.toBeNull(); }); + + it('should use optionText with a function value as text identifier when a create element is passed', () => { + const choices = [ + { id: 't', foobar: 'Technical' }, + { id: 'p', foobar: 'Programming' }, + ]; + const newChoice = { id: 'js_fatigue', foobar: 'New Kid On The Block' }; + + const Create = () => { + const context = useCreateSuggestionContext(); + const handleClick = () => { + choices.push(newChoice); + context.onCreate(newChoice); + }; + + return ; + }; + + const { getByLabelText, getByText, queryAllByRole } = render( +
( + } + optionText={choice => choice.foobar} + choices={choices} + /> + )} + /> + ); + + fireEvent.focus( + getByLabelText('resources.posts.fields.tags', { + selector: 'input', + }) + ); + + expect(queryAllByRole('option')).toHaveLength(3); + expect(getByText('Technical')).not.toBeNull(); + expect(getByText('Programming')).not.toBeNull(); + expect(getByText('ra.action.create')).not.toBeNull(); + }); + + it('should support creation of a new choice through the onCreate event when optionText is a function', async () => { + const choices = [ + { id: 'ang', name: 'Angular' }, + { id: 'rea', name: 'React' }, + ]; + const handleCreate = filter => { + const newChoice = { + id: 'js_fatigue', + name: filter, + }; + choices.push(newChoice); + return newChoice; + }; + + const { getByLabelText, getByText, queryByText, rerender } = render( + ( + `Choice is ${choice.name}`} + /> + )} + /> + ); + + const input = getByLabelText('resources.posts.fields.language', { + selector: 'input', + }) as HTMLInputElement; + input.focus(); + fireEvent.change(input, { target: { value: 'New Kid On The Block' } }); + fireEvent.click(getByText('Choice is ra.action.create_item')); + await new Promise(resolve => setTimeout(resolve)); + rerender( + ( + `Choice is ${choice.name}`} + /> + )} + /> + ); + + expect(queryByText('Choice is New Kid On The Block')).not.toBeNull(); + }); }); diff --git a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.tsx index b1b0e01b027..7374aa8aad2 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteArrayInput.tsx @@ -290,6 +290,8 @@ const AutocompleteArrayInput = (props: AutocompleteArrayInputProps) => { optionText, }); + const createItem = create || onCreate ? getCreateItem() : null; + const handleDelete = useCallback( item => () => { const newSelectedItems = [...selectedItems]; @@ -410,7 +412,7 @@ const AutocompleteArrayInput = (props: AutocompleteArrayInputProps) => { const suggestions = [ ...getSuggestions(suggestionFilter), - ...(onCreate || create ? [getCreateItem()] : []), + ...(!!createItem ? [createItem] : []), ]; return (
@@ -438,8 +440,9 @@ const AutocompleteArrayInput = (props: AutocompleteArrayInputProps) => { variant === 'outlined', })} > - {selectedItems.map( - (item, index) => ( + {selectedItems + .filter(item => !!item) + .map((item, index) => ( { item )} /> - ) - )} + ))}
), endAdornment: loading && ( @@ -519,7 +521,7 @@ const AutocompleteArrayInput = (props: AutocompleteArrayInputProps) => { {suggestions.map((suggestion, index) => (