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
17 changes: 6 additions & 11 deletions frontend/src/components/CippComponents/CippAutocomplete.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -47,7 +39,7 @@ const MemoTextField = React.memo(function MemoTextField({
{...otherParams}
label={label}
placeholder={placeholder}
{...otherProps}
variant={variant}
required={htmlRequired}
slotProps={{
inputLabel: {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -619,13 +613,14 @@ export const CippAutoComplete = React.forwardRef((props, ref) => {

return (
<Stack direction="row" spacing={1}>
{/* caller props stay on <Autocomplete>, anything spread here reaches the input as a DOM attr */}
<MemoTextField
params={{ ...otherParams, InputProps: modifiedInputProps }}
label={label}
placeholder={placeholder}
variant={variant}
required={required}
htmlRequired={required && !hasSelection}
{...other}
/>
{api?.url && api?.showRefresh && (
<Tooltip title="Refresh">
Expand Down
47 changes: 47 additions & 0 deletions frontend/tests/components/CippComponents/CippAutocomplete.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<CippAutoComplete
multiple={false}
creatable={false}
options={OPTIONS}
onChange={() => {}}
noOptionsText="nothing here"
/>
)
expect(container.querySelector('[nooptionstext]')).toBeNull()
})

it('routes variant to the text field, not to the autocomplete root', () => {
const { container } = renderWithProviders(
<CippAutoComplete
multiple={false}
creatable={false}
options={OPTIONS}
onChange={() => {}}
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(
<CippAutoComplete
multiple={false}
creatable={false}
options={OPTIONS}
value={OPTIONS[0]}
onChange={() => {}}
filterSelectedOptions={false}
/>
)
await user.click(screen.getByRole('combobox'))
expect(await screen.findByRole('option', { name: 'Alpha' })).toBeInTheDocument()
})
})
})
Loading