Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 67 additions & 0 deletions packages/mui-material/src/Autocomplete/Autocomplete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3374,6 +3374,73 @@ describe('<Autocomplete />', () => {
expect(handleChange.args[0][1]).to.equal('The');
});

it('should prevent form submission when committing typed text over auto-highlighted match', async () => {
const handleChange = spy();
const handleSubmit = spy();
const options = ['The Shawshank Redemption', 'The Godfather'];
const { user } = render(
<div
onKeyDown={(event) => {
if (!event.defaultPrevented && event.key === 'Enter') {
handleSubmit();
}
}}
>
<Autocomplete
freeSolo
autoHighlight
openOnFocus
options={options}
onChange={handleChange}
renderInput={(params) => <TextField {...params} autoFocus />}
/>
</div>,
);

await user.type(screen.getByRole('combobox'), 'The{Enter}');

expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal('The');
expect(handleSubmit.callCount).to.equal(0);

await user.keyboard('{Enter}');

expect(handleSubmit.callCount).to.equal(1);
});

it('should prevent form submission when committing edited selected text over value-highlighted match', async () => {
const handleChange = spy();
const handleSubmit = spy();
const options = ['The Shawshank Redemption', 'The Godfather'];
const { user } = render(
<div
onKeyDown={(event) => {
if (!event.defaultPrevented && event.key === 'Enter') {
handleSubmit();
}
}}
>
<Autocomplete
freeSolo
defaultValue="The Godfather"
options={options}
onChange={handleChange}
renderInput={(params) => <TextField {...params} autoFocus />}
/>
</div>,
);

await user.keyboard('{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}{Enter}');

expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal('The Godf');
expect(handleSubmit.callCount).to.equal(0);

await user.keyboard('{Enter}');

expect(handleSubmit.callCount).to.equal(1);
});

it('should prefer typed text after editing a selected value', async () => {
const handleChange = spy();
const options = ['The Shawshank Redemption', 'The Godfather'];
Expand Down
23 changes: 14 additions & 9 deletions packages/mui-material/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -1016,14 +1016,19 @@ function useAutocomplete(props) {
}
break;
case 'Enter': {
// In freeSolo, only select the highlighted option if the user hasn't
// typed new text (inputPristine) or explicitly interacted with an option
// (keyboard, mouse, or touch — any non-null reason). This lets typed
// text win over a programmatic highlight (reason=null, e.g. from
// syncHighlightedIndex matching a previous value) while still honoring
// deliberate user interactions like hovering a suggestion then pressing Enter.
// In freeSolo, once the user has typed new text, Enter should commit that text instead
// of treating a programmatic highlight as an intentional selection. Programmatic
// highlights include autoHighlight and syncHighlightedIndex matching a previous value.
const hasProgrammaticHighlight =
popupOpen &&
highlightedIndexRef.current !== -1 &&
highlightReasonRef.current === null;
const shouldCommitFreeSoloOverProgrammaticHighlight =
freeSolo && !inputPristine && hasProgrammaticHighlight;
const shouldSelectHighlighted =
!freeSolo || inputPristine || highlightReasonRef.current !== null;
!freeSolo || inputPristine || !hasProgrammaticHighlight;
const shouldPreventSubmitAfterFreeSoloCommit =
shouldCommitFreeSoloOverProgrammaticHighlight && !touchScrolledRef.current;

if (
highlightedIndexRef.current !== -1 &&
Expand Down Expand Up @@ -1053,8 +1058,8 @@ function useAutocomplete(props) {
);
}
} else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
if (multiple) {
// Allow people to add new values before they submit the form.
if (multiple || shouldPreventSubmitAfterFreeSoloCommit) {
// Allow people to commit the freeSolo value before they submit the form.
event.preventDefault();
}
selectNewValue(event, inputValue, 'createOption', 'freeSolo');
Expand Down
Loading