From 1bcdc6fda82127f1c89d7c03bd6b3286c5ff8091 Mon Sep 17 00:00:00 2001 From: k-grube Date: Tue, 11 Aug 2026 00:09:01 -0700 Subject: [PATCH] Fix(frontend): remove autocomplete prop spread, keep autocomplete props off input DOM node --- .../CippComponents/CippAutocomplete.jsx | 17 +++---- .../CippComponents/CippAutocomplete.test.jsx | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/CippComponents/CippAutocomplete.jsx b/frontend/src/components/CippComponents/CippAutocomplete.jsx index 5a1953382..0073f4e01 100644 --- a/frontend/src/components/CippComponents/CippAutocomplete.jsx +++ b/frontend/src/components/CippComponents/CippAutocomplete.jsx @@ -24,20 +24,12 @@ const MemoTextField = React.memo(function MemoTextField({ params, label, placeholder, + variant, // Field-level required: asterisk on the label. HTML5 required is separate because // Autocomplete (especially multiple) clears the input after selection — a static // required on the input would falsely block submit even when chips/value exist. required = false, htmlRequired = false, - // Autocomplete-specific props that must not be forwarded to TextField/DOM - getOptionLabel, - isOptionEqualToValue, - filterOptions, - getOptionDisabled, - groupBy, - renderGroup, - renderOption, - ...otherProps }) { const { InputProps, ...otherParams } = params @@ -47,7 +39,7 @@ const MemoTextField = React.memo(function MemoTextField({ {...otherParams} label={label} placeholder={placeholder} - {...otherProps} + variant={variant} required={htmlRequired} slotProps={{ inputLabel: { @@ -96,6 +88,8 @@ export const CippAutoComplete = React.forwardRef((props, ref) => { renderGroup, customAction, handleHomeEndKeys = false, + // TextField-bound, MUI Autocomplete would pass it through to its root div + variant, ...other } = props @@ -619,13 +613,14 @@ export const CippAutoComplete = React.forwardRef((props, ref) => { return ( + {/* caller props stay on , anything spread here reaches the input as a DOM attr */} {api?.url && api?.showRefresh && ( diff --git a/frontend/tests/components/CippComponents/CippAutocomplete.test.jsx b/frontend/tests/components/CippComponents/CippAutocomplete.test.jsx index e5a47bc58..90dbd2aa6 100644 --- a/frontend/tests/components/CippComponents/CippAutocomplete.test.jsx +++ b/frontend/tests/components/CippComponents/CippAutocomplete.test.jsx @@ -345,4 +345,51 @@ describe('CippAutoComplete', () => { expect(document.querySelector('.MuiFormLabel-asterisk')).toBeTruthy() }) }) + + // TextField forwards what it doesn't consume to the FormControl root, so a leak lands as a DOM attr + describe('prop routing', () => { + it('keeps autocomplete-only props off the DOM', () => { + const { container } = renderWithProviders( + {}} + noOptionsText="nothing here" + /> + ) + expect(container.querySelector('[nooptionstext]')).toBeNull() + }) + + it('routes variant to the text field, not to the autocomplete root', () => { + const { container } = renderWithProviders( + {}} + variant="outlined" + /> + ) + // outlined draws the notched fieldset/legend, the themed filled default does not + expect(container.querySelector('fieldset legend')).toBeTruthy() + expect(container.querySelector('[variant]')).toBeNull() + }) + + it('forwards filterSelectedOptions to the autocomplete, selected option stays listed', async () => { + const user = userEvent.setup() + renderWithProviders( + {}} + filterSelectedOptions={false} + /> + ) + await user.click(screen.getByRole('combobox')) + expect(await screen.findByRole('option', { name: 'Alpha' })).toBeInTheDocument() + }) + }) })