Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui-materialui]: Hide <AutoCompleteInput> "Create" option for empty input #10228

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7382c27
Add "standalaone usage" doc section in dialog views
erwanMarmelab Sep 19, 2024
85e5088
fix code examples
erwanMarmelab Sep 21, 2024
5648e80
fix the tip documentation
erwanMarmelab Sep 21, 2024
eb0cecf
documentate limitation
erwanMarmelab Sep 21, 2024
68bce53
add support for Edit emptyWhileLoading
erwanMarmelab Sep 21, 2024
60cbc3b
document Edit emptyWhileLoading
erwanMarmelab Sep 21, 2024
4514f75
add a story for Edit emptyWhileLoading
erwanMarmelab Sep 21, 2024
58b724d
hide “Create” on empty input and any createLabel
erwanMarmelab Sep 22, 2024
0b9d66e
update old test and create a new one
erwanMarmelab Sep 22, 2024
3462379
Revert "add a story for Edit emptyWhileLoading"
erwanMarmelab Sep 25, 2024
48eb05a
Revert "document Edit emptyWhileLoading"
erwanMarmelab Sep 25, 2024
e61fe08
Revert "add support for Edit emptyWhileLoading"
erwanMarmelab Sep 25, 2024
60630d1
Revert "documentate limitation"
erwanMarmelab Sep 25, 2024
b7fc372
Revert "fix the tip documentation"
erwanMarmelab Sep 25, 2024
50f24e3
Revert "fix code examples"
erwanMarmelab Sep 25, 2024
b0a80ed
Revert "Add "standalaone usage" doc section in dialog views"
erwanMarmelab Sep 25, 2024
05b94d9
add a story for <AutocompleteInput createItemLabel>
erwanMarmelab Sep 25, 2024
c81d1be
fix the create flash `Create xxx` -> `xxx`
erwanMarmelab Sep 25, 2024
ddf3549
test the fix
erwanMarmelab Sep 25, 2024
13342c6
use AutocompleteWithCreateInReferenceInput for all creations
erwanMarmelab Sep 27, 2024
10a1fc3
revert useless changes + fix creation
erwanMarmelab Oct 1, 2024
6d4983b
rename createOption into createOptionLabel
erwanMarmelab Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 75 additions & 4 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
InsideReferenceInputOnChange,
WithInputProps,
OnCreate,
OnCreateSlow,
} from './AutocompleteInput.stories';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
Expand Down Expand Up @@ -515,7 +516,7 @@ describe('<AutocompleteInput />', () => {
];
const OptionText = () => {
const record = useRecordContext();
return <span>option:{record.name}</span>;
return <span>option:{record?.name}</span>;
};
render(
<AdminContext>
Expand Down Expand Up @@ -1054,7 +1055,7 @@ describe('<AutocompleteInput />', () => {
});

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' },
Expand Down Expand Up @@ -1093,7 +1094,53 @@ describe('<AutocompleteInput />', () => {
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(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm
mode="onBlur"
onSubmit={jest.fn()}
defaultValues={{ language: 'ang' }}
>
<AutocompleteInput
source="language"
resource="posts"
choices={choices}
onCreate={handleCreate}
createLabel="Start typing to create a new item"
/>
</SimpleForm>
</AdminContext>
);

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 () => {
Expand Down Expand Up @@ -1245,7 +1292,6 @@ describe('<AutocompleteInput />', () => {
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' },
Expand Down Expand Up @@ -1314,6 +1360,31 @@ describe('<AutocompleteInput />', () => {
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(<OnCreateSlow />);
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 () => {
Expand Down
47 changes: 28 additions & 19 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,33 +357,42 @@ export const CreateLabel = () => (
<Wrapper>
<AutocompleteInput
source="author"
choices={[
{ id: 1, name: 'Leo Tolstoy' },
{ id: 2, name: 'Victor Hugo' },
{ id: 3, name: 'William Shakespeare' },
{ id: 4, name: 'Charles Baudelaire' },
{ id: 5, name: 'Marcel Proust' },
]}
choices={choicesForCreationSupport}
onCreate={filter => {
const newAuthorName = window.prompt(
'Enter a new author',
filter
);
Comment on lines -368 to -371
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the prompt from this story (which is a good thing IMO because it was redundant with the ability to fill in the filter), reveals a pre-existing odd behavior: if I click on the "Start typing to create a new item" label, then the value is filled with "Start typing to create a new item", which is probably not what I want. This special entry in the list should not be clickable IMO.
But this can be addressed in another PR since it was already existing.

Capture.video.2024-10-08.18.21.45.mp4

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had issues while testing stories like this one, or OnCreate, which I think are due to the fact that running choicesForCreationSupport.push(newOption) does not always trigger a rerender.
I believe choicesForCreationSupport should be turned into a React State to avoid such problems.

In the following recording, the first item I create ('new') does not appear immediately, nor does it appear in the list. But when I create a second item ('new2'), then, suddenly, both items appear in the list.

Capture.video.2024-10-08.18.16.39.mp4

return newOption;
}}
createLabel="Start typing to create a new item"
/>
</Wrapper>
);

export const CreateItemLabel = () => (
<Wrapper>
<AutocompleteInput
source="author"
choices={choicesForCreationSupport}
onCreate={filter => {
if (!filter) return;

const newOption = {
id: choicesForCreationSupport.length + 1,
name: filter,
};
choicesForCreationSupport.push(newOption);
return newOption;
}}
createItemLabel="Add a new author: %{item}"
/>
</Wrapper>
);

const authorsWithFirstAndLastName = [
{ id: 1, first_name: 'Leo', last_name: 'Tolstoy', language: 'Russian' },
{ id: 2, first_name: 'Victor', last_name: 'Hugo', language: 'French' },
Expand Down
12 changes: 9 additions & 3 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const AutocompleteInput = <
closeText = 'ra.action.close',
create,
createLabel,
createItemLabel,
createItemLabel = 'ra.action.create_item',
createValue,
debounce: debounceDelay = 250,
defaultValue,
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the docs should be updated to reflect this change: https://marmelab.com/react-admin/AutocompleteInput.html#createlabel

// create option with createLabel
filteredOptions = filteredOptions.concat(getCreateItem(''));
} else if (!doesQueryMatchSuggestion(filterValue)) {
Expand Down
Loading