Skip to content

Commit

Permalink
[Select]Bug when the first child is a ListSubheader (#27299)
Browse files Browse the repository at this point in the history
  • Loading branch information
DouglasPds committed Apr 11, 2022
1 parent cf7af44 commit a8db12e
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 2 deletions.
83 changes: 83 additions & 0 deletions packages/mui-material/src/Select/Select.test.js
Expand Up @@ -11,6 +11,7 @@ import {
} from 'test/utils';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import MenuItem from '@mui/material/MenuItem';
import ListSubheader from '@mui/material/ListSubheader';
import InputBase from '@mui/material/InputBase';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputLabel from '@mui/material/InputLabel';
Expand Down Expand Up @@ -484,6 +485,88 @@ describe('<Select />', () => {
expect(getAllByRole('option')[1]).to.have.attribute('aria-selected', 'true');
});

describe('when the first child is a ListSubheader', () => {
it('first selectable option is focused to use the arrow', () => {
const { getAllByRole } = render(
<Select defaultValue="" open>
<ListSubheader>Category 1</ListSubheader>
<MenuItem value={1}>Option 1</MenuItem>
<MenuItem value={2}>Option 2</MenuItem>
<ListSubheader>Category 2</ListSubheader>
<MenuItem value={3}>Option 3</MenuItem>
<MenuItem value={4}>Option 4</MenuItem>
</Select>,
);

const options = getAllByRole('option');
expect(options[1]).to.have.attribute('tabindex', '0');

act(() => {
fireEvent.keyDown(options[1], { key: 'ArrowDown' });
fireEvent.keyDown(options[2], { key: 'ArrowDown' });
fireEvent.keyDown(options[4], { key: 'Enter' });
});

expect(options[4]).to.have.attribute('aria-selected', 'true');
});

describe('when also the second child is a ListSubheader', () => {
it('first selectable option is focused to use the arrow', () => {
const { getAllByRole } = render(
<Select defaultValue="" open>
<ListSubheader>Empty category</ListSubheader>
<ListSubheader>Category 1</ListSubheader>
<MenuItem value={1}>Option 1</MenuItem>
<MenuItem value={2}>Option 2</MenuItem>
<ListSubheader>Category 2</ListSubheader>
<MenuItem value={3}>Option 3</MenuItem>
<MenuItem value={4}>Option 4</MenuItem>
</Select>,
);

const options = getAllByRole('option');
expect(options[2]).to.have.attribute('tabindex', '0');

act(() => {
fireEvent.keyDown(options[2], { key: 'ArrowDown' });
fireEvent.keyDown(options[3], { key: 'ArrowDown' });
fireEvent.keyDown(options[5], { key: 'Enter' });
});

expect(options[5]).to.have.attribute('aria-selected', 'true');
});
});
});

describe('when the first child is a MenuItem disabled', () => {
it('first selectable option is focused to use the arrow', () => {
const { getAllByRole } = render(
<Select defaultValue="" open>
<MenuItem value="" disabled>
<em>None</em>
</MenuItem>
<ListSubheader>Category 1</ListSubheader>
<MenuItem value={1}>Option 1</MenuItem>
<MenuItem value={2}>Option 2</MenuItem>
<ListSubheader>Category 2</ListSubheader>
<MenuItem value={3}>Option 3</MenuItem>
<MenuItem value={4}>Option 4</MenuItem>
</Select>,
);

const options = getAllByRole('option');
expect(options[2]).to.have.attribute('tabindex', '0');

act(() => {
fireEvent.keyDown(options[2], { key: 'ArrowDown' });
fireEvent.keyDown(options[3], { key: 'ArrowDown' });
fireEvent.keyDown(options[5], { key: 'Enter' });
});

expect(options[5]).to.have.attribute('aria-selected', 'true');
});
});

it('it will fallback to its content for the accessible name when it has no name', () => {
const { getByRole } = render(<Select value="" />);

Expand Down
27 changes: 25 additions & 2 deletions packages/mui-material/src/Select/SelectInput.js
Expand Up @@ -348,7 +348,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
}
}

const items = childrenArray.map((child) => {
const items = childrenArray.map((child, index, arr) => {
if (!React.isValidElement(child)) {
return null;
}
Expand Down Expand Up @@ -389,6 +389,26 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
foundMatch = true;
}

if (child.props.value === undefined) {
return React.cloneElement(child, {
'aria-readonly': true,
role: 'option',
});
}

const isFirstSelectableElement = () => {
if (value) {
return selected;
}
const firstSelectableElement = arr.find(
(item) => item.props.value !== undefined && item.props.disabled !== true,
);
if (child === firstSelectableElement) {
return true;
}
return selected;
};

return React.cloneElement(child, {
'aria-selected': selected ? 'true' : 'false',
onClick: handleItemClick(child),
Expand All @@ -405,7 +425,10 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
}
},
role: 'option',
selected,
selected:
arr[0].props.value === undefined || arr[0].props.disabled === true
? isFirstSelectableElement()
: selected,
value: undefined, // The value is most likely not a valid HTML attribute.
'data-value': child.props.value, // Instead, we provide it as a data attribute.
});
Expand Down

0 comments on commit a8db12e

Please sign in to comment.