diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx index 43255b37d6..e1c23c994f 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx @@ -26,6 +26,7 @@ import { InsideReferenceInputOnChange, WithInputProps, OnCreate, + OnCreateSlow, } from './AutocompleteInput.stories'; import { ReferenceArrayInput } from './ReferenceArrayInput'; import { AutocompleteArrayInput } from './AutocompleteArrayInput'; @@ -515,7 +516,7 @@ describe('', () => { ]; const OptionText = () => { const record = useRecordContext(); - return option:{record.name}; + return option:{record?.name}; }; render( @@ -1054,7 +1055,7 @@ describe('', () => { }); describe('onCreate', () => { - it('should include an option with the createLabel when the input is empty', async () => { + it("shouldn't include an option with the createLabel when the input is empty", async () => { const choices = [ { id: 'ang', name: 'Angular' }, { id: 'rea', name: 'React' }, @@ -1093,7 +1094,53 @@ describe('', () => { target: { value: '' }, }); - expect(screen.queryByText('ra.action.create')).not.toBeNull(); + expect(screen.queryByText('ra.action.create')).toBeNull(); + expect(screen.queryByText('ra.action.create_item')).toBeNull(); + }); + it('should include an option with the custom createLabel when the input is empty', 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; + }; + + render( + + + + + + ); + + const input = screen.getByLabelText( + 'resources.posts.fields.language' + ) as HTMLInputElement; + input.focus(); + fireEvent.change(input, { + target: { value: '' }, + }); + + expect( + screen.queryByText('Start typing to create a new item') + ).not.toBeNull(); + expect(screen.queryByText('ra.action.create')).toBeNull(); expect(screen.queryByText('ra.action.create_item')).toBeNull(); }); it('should include an option with the createItemLabel when the input not empty', async () => { @@ -1245,7 +1292,6 @@ describe('', () => { fireEvent.focus(input); expect(screen.queryByText('New Kid On The Block')).not.toBeNull(); }); - it('should allow the creation of a new choice with a promise', async () => { const choices = [ { id: 'ang', name: 'Angular' }, @@ -1314,6 +1360,31 @@ describe('', () => { fireEvent.focus(input); expect(screen.queryByText('New Kid On The Block')).not.toBeNull(); }); + it('should not use the createItemLabel as the value of the input', async () => { + render(); + await screen.findByText('Book War and Peace', undefined, { + timeout: 2000, + }); + const input = screen.getByLabelText('Author') as HTMLInputElement; + await waitFor( + () => { + expect(input.value).toBe('Leo Tolstoy'); + }, + { timeout: 2000 } + ); + fireEvent.focus(input); + expect(screen.getAllByRole('option')).toHaveLength(4); + fireEvent.change(input, { target: { value: 'x' } }); + await waitFor( + () => { + expect(screen.getAllByRole('option')).toHaveLength(1); + }, + { timeout: 2000 } + ); + fireEvent.click(screen.getByText('Create x')); + expect(input.value).not.toBe('Create x'); + expect(input.value).toBe('x'); + }, 10000); }); describe('create', () => { it('should allow the creation of a new choice', async () => { diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx index 4d0a33bf43..f0cab76721 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx @@ -357,33 +357,42 @@ export const CreateLabel = () => ( { - const newAuthorName = window.prompt( - 'Enter a new author', - filter - ); + if (!filter) return; - if (newAuthorName) { - const newAuthor = { - id: choicesForCreationSupport.length + 1, - name: newAuthorName, - }; - choicesForCreationSupport.push(newAuthor); - return newAuthor; - } + const newOption = { + id: choicesForCreationSupport.length + 1, + name: filter, + }; + choicesForCreationSupport.push(newOption); + return newOption; }} createLabel="Start typing to create a new item" /> ); +export const CreateItemLabel = () => ( + + { + if (!filter) return; + + const newOption = { + id: choicesForCreationSupport.length + 1, + name: filter, + }; + choicesForCreationSupport.push(newOption); + return newOption; + }} + createItemLabel="Add a new author: %{item}" + /> + +); + const authorsWithFirstAndLastName = [ { id: 1, first_name: 'Leo', last_name: 'Tolstoy', language: 'Russian' }, { id: 2, first_name: 'Victor', last_name: 'Hugo', language: 'French' }, diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx index 008a4d52da..576aaa7d49 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx @@ -133,7 +133,7 @@ export const AutocompleteInput = < closeText = 'ra.action.close', create, createLabel, - createItemLabel, + createItemLabel = 'ra.action.create_item', createValue, debounce: debounceDelay = 250, defaultValue, @@ -465,7 +465,13 @@ If you provided a React element for the optionText prop, you must also provide t event?.type === 'change' || !doesQueryMatchSelection(newInputValue) ) { - setFilterValue(newInputValue); + const createOptionLabel = translate(createItemLabel, { + item: filterValue, + _: createItemLabel, + }); + const isCreate = newInputValue === createOptionLabel; + const valueToSet = isCreate ? filterValue : newInputValue; + setFilterValue(valueToSet); debouncedSetFilter(newInputValue); } if (reason === 'clear') { @@ -513,7 +519,7 @@ If you provided a React element for the optionText prop, you must also provide t // add create option if necessary const { inputValue } = params; if (onCreate || create) { - if (inputValue === '') { + if (inputValue === '' && createLabel) { // create option with createLabel filteredOptions = filteredOptions.concat(getCreateItem('')); } else if (!doesQueryMatchSuggestion(filterValue)) {