Repository: mui/material-ui (TypeScript) — Bug Fix

Issue:
[Select] Aria error flagged by WAVE and W3C
<!-- Provide a general summary of the issue in the Title above -->
WAVE errors in tables, in TablePagination/TableFooter
<!--
  Thank you very much for contributing to Material-UI by creating an issue! ❤️
  To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->

- [ x] The issue is present in the latest release.
- [x ] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯
1. Go to https://material-ui.com/components/tables/
2. Run WAVE
3. Should not get errors from standard components 
<!-- Describe what happens instead of the expected behavior. -->
WAVE identifies 3 "Broken ARIA Reference Errors" associated with three demos
- Sorting & Selection
- Fixed Headers
- Editable Example - this is a material-table which seems to inherit the accessibility bug from TableFooter 


## Expected Behavior 🤔
No WAVE errors
<!-- Describe what should happen. -->
No WAVE errors
## Steps to Reproduce 🕹
Given above
<!--
  Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
  Include code to reproduce, if relevant (which it most likely is).

  You should use the official codesandbox template as a starting point: https://material-ui.com/r/issue-template

  If you have an issue concerning TypeScript please start from this TypeScript playground: https://material-ui.com/r/ts-issue-template

  Issues without some form of live example have a longer response time.
-->

Steps:

1.
2.
3.
4.

## Context 🔦

<!--
  What are you trying to accomplish? How has this issue affected you?
  Providing context helps us come up with a solution that is most useful in the real world.
-->

## Your Environment 🌎
OSX Mojave, Chrome v80, WAVE v3.0.4
<!--
  Include as many relevant details about the environment with which you experienced the bug.
  If you encounter issues with typescript please include version and tsconfig.
-->

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.9.6  |
| React       |         |
| Browser     |         |
| TypeScript  |         |
| etc.        |         |



Discussion:
Thanks for opening this issue.

Does WAVE provide more information beyond "Broken ARIA Reference Errors"?
## Broken ARIA reference

### What It Means

An aria-labelledby or aria-describedby reference exists, but the target for the reference does not exist.

### Why It Matters

ARIA labels and descriptions will not be presented if the element referenced does not exist in the page.

### How to Fix It
Ensure the element referenced in the aria-labelledby or aria-describedby attribute value is present within the page and presents a proper label or description.

### The Algorithm... in English

An element has an aria-labelledby or aria-describedby value that does not match the id attribute value of another element in the page.

### Standards and Guidelines

[1.3.1 Info and Relationships (Level A)](https://webaim.org/standards/wcag/checklist#sc1.3.1)
[4.1.2 Name, Role, Value (Level A)](https://webaim.org/standards/wcag/checklist#sc4.1.2)
@mfsjr Thanks for the report.

While this issue **focuses on the reported errors**. WAVE also reports less important alerts, I will take care of it in batch (e.g. broken anchor link).
@eps1lon If you would prefer working on it, I can leave it to you. Let me know, I wouldn't want to distract you from the prop-types -> TypeScript migration of the descriptions :D.
@oliviertassinari No perfectly fine. That's why I assigned it to you and unsubscribed. But you had to mention me again :stuck_out_tongue: 
Thank you @oliviertassinari , @eps1lon for such quick response!

Resolving patch:
diff --git a/docs/src/pages/components/selects/MultipleSelect.js b/docs/src/pages/components/selects/MultipleSelect.js
--- a/docs/src/pages/components/selects/MultipleSelect.js
+++ b/docs/src/pages/components/selects/MultipleSelect.js
@@ -161,6 +161,7 @@ export default function MultipleSelect() {
             return selected.join(', ');
           }}
           MenuProps={MenuProps}
+          inputProps={{ 'aria-label': 'Without label' }}
         >
           <MenuItem disabled value="">
             <em>Placeholder</em>
diff --git a/docs/src/pages/components/selects/MultipleSelect.tsx b/docs/src/pages/components/selects/MultipleSelect.tsx
--- a/docs/src/pages/components/selects/MultipleSelect.tsx
+++ b/docs/src/pages/components/selects/MultipleSelect.tsx
@@ -163,6 +163,7 @@ export default function MultipleSelect() {
             return (selected as string[]).join(', ');
           }}
           MenuProps={MenuProps}
+          inputProps={{ 'aria-label': 'Without label' }}
         >
           <MenuItem disabled value="">
             <em>Placeholder</em>
diff --git a/docs/src/pages/components/selects/SimpleSelect.js b/docs/src/pages/components/selects/SimpleSelect.js
--- a/docs/src/pages/components/selects/SimpleSelect.js
+++ b/docs/src/pages/components/selects/SimpleSelect.js
@@ -57,7 +57,13 @@ export default function SimpleSelect() {
         <FormHelperText>Some important helper text</FormHelperText>
       </FormControl>
       <FormControl className={classes.formControl}>
-        <Select value={age} onChange={handleChange} displayEmpty className={classes.selectEmpty}>
+        <Select
+          value={age}
+          onChange={handleChange}
+          displayEmpty
+          className={classes.selectEmpty}
+          inputProps={{ 'aria-label': 'Without label' }}
+        >
           <MenuItem value="">
             <em>None</em>
           </MenuItem>
@@ -160,7 +166,13 @@ export default function SimpleSelect() {
         <FormHelperText>Auto width</FormHelperText>
       </FormControl>
       <FormControl className={classes.formControl}>
-        <Select value={age} onChange={handleChange} displayEmpty className={classes.selectEmpty}>
+        <Select
+          value={age}
+          onChange={handleChange}
+          displayEmpty
+          className={classes.selectEmpty}
+          inputProps={{ 'aria-label': 'Without label' }}
+        >
           <MenuItem value="" disabled>
             Placeholder
           </MenuItem>
diff --git a/docs/src/pages/components/selects/SimpleSelect.tsx b/docs/src/pages/components/selects/SimpleSelect.tsx
--- a/docs/src/pages/components/selects/SimpleSelect.tsx
+++ b/docs/src/pages/components/selects/SimpleSelect.tsx
@@ -59,7 +59,13 @@ export default function SimpleSelect() {
         <FormHelperText>Some important helper text</FormHelperText>
       </FormControl>
       <FormControl className={classes.formControl}>
-        <Select value={age} onChange={handleChange} displayEmpty className={classes.selectEmpty}>
+        <Select
+          value={age}
+          onChange={handleChange}
+          displayEmpty
+          className={classes.selectEmpty}
+          inputProps={{ 'aria-label': 'Without label' }}
+        >
           <MenuItem value="">
             <em>None</em>
           </MenuItem>
@@ -162,7 +168,13 @@ export default function SimpleSelect() {
         <FormHelperText>Auto width</FormHelperText>
       </FormControl>
       <FormControl className={classes.formControl}>
-        <Select value={age} onChange={handleChange} displayEmpty className={classes.selectEmpty}>
+        <Select
+          value={age}
+          onChange={handleChange}
+          displayEmpty
+          className={classes.selectEmpty}
+          inputProps={{ 'aria-label': 'Without label' }}
+        >
           <MenuItem value="" disabled>
             Placeholder
           </MenuItem>
diff --git a/packages/material-ui/src/Select/SelectInput.js b/packages/material-ui/src/Select/SelectInput.js
--- a/packages/material-ui/src/Select/SelectInput.js
+++ b/packages/material-ui/src/Select/SelectInput.js
@@ -26,6 +26,7 @@ function isEmpty(display) {
  */
 const SelectInput = React.forwardRef(function SelectInput(props, ref) {
   const {
+    'aria-label': ariaLabel,
     autoFocus,
     autoWidth,
     children,
@@ -320,8 +321,9 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
         tabIndex={tabIndex}
         role="button"
         aria-expanded={open ? 'true' : undefined}
-        aria-labelledby={`${labelId || ''} ${buttonId || ''}`}
         aria-haspopup="listbox"
+        aria-label={ariaLabel}
+        aria-labelledby={[labelId, buttonId].filter(Boolean).join(' ') || undefined}
         onKeyDown={handleKeyDown}
         onMouseDown={disabled || readOnly ? null : handleMouseDown}
         onBlur={handleBlur}
@@ -379,6 +381,10 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
 });
 
 SelectInput.propTypes = {
+  /**
+   * @ignore
+   */
+  'aria-label': PropTypes.string,
   /**
    * @ignore
    */
diff --git a/packages/material-ui/src/TablePagination/TablePagination.js b/packages/material-ui/src/TablePagination/TablePagination.js
--- a/packages/material-ui/src/TablePagination/TablePagination.js
+++ b/packages/material-ui/src/TablePagination/TablePagination.js
@@ -35,9 +35,9 @@ export const styles = (theme) => ({
   caption: {
     flexShrink: 0,
   },
+  // TODO v5: `.selectRoot` should be merged with `.input`
   /* Styles applied to the Select component root element. */
   selectRoot: {
-    // `.selectRoot` should be merged with `.input` in v5.
     marginRight: 32,
     marginLeft: 8,
   },
@@ -122,6 +122,7 @@ const TablePagination = React.forwardRef(function TablePagination(props, ref) {
             input={<InputBase className={clsx(classes.input, classes.selectRoot)} />}
             value={rowsPerPage}
             onChange={onChangeRowsPerPage}
+            inputProps={{ 'aria-label': labelRowsPerPage }}
             {...SelectProps}
           >
             {rowsPerPageOptions.map((rowsPerPageOption) => (


Test patch:
diff --git a/packages/material-ui/src/Select/Select.test.js b/packages/material-ui/src/Select/Select.test.js
--- a/packages/material-ui/src/Select/Select.test.js
+++ b/packages/material-ui/src/Select/Select.test.js
@@ -409,7 +409,7 @@ describe('<Select />', () => {
       const { getByRole } = render(<Select value="" />);
 
       // TODO what is the accessible name actually?
-      expect(getByRole('button')).to.have.attribute('aria-labelledby', ' ');
+      expect(getByRole('button')).to.not.have.attribute('aria-labelledby');
     });
 
     it('is labelled by itself when it has a name', () => {
@@ -417,7 +417,7 @@ describe('<Select />', () => {
 
       expect(getByRole('button')).to.have.attribute(
         'aria-labelledby',
-        ` ${getByRole('button').getAttribute('id')}`,
+        getByRole('button').getAttribute('id'),
       );
     });