diff --git a/.changeset/stupid-seas-stare.md b/.changeset/stupid-seas-stare.md
new file mode 100644
index 0000000000..f3d4f5e1f7
--- /dev/null
+++ b/.changeset/stupid-seas-stare.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Fix accessibility issue on controlled select input on tab navigation + keyboard enter/space action.
diff --git a/packages/core-components/src/components/Select/Select.test.tsx b/packages/core-components/src/components/Select/Select.test.tsx
index e9828e70fd..71159150e4 100644
--- a/packages/core-components/src/components/Select/Select.test.tsx
+++ b/packages/core-components/src/components/Select/Select.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { fireEvent, render } from '@testing-library/react';
+import { fireEvent, render, within } from '@testing-library/react';
import React from 'react';
import { SelectComponent as Select } from './Select';
@@ -45,7 +45,7 @@ describe('', () => {
expect(input.textContent).toBe('All results');
// Simulate click on input
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
expect(getByText('test 1')).toBeInTheDocument();
const option = getByText('test 1');
diff --git a/packages/core-components/src/components/Select/Select.tsx b/packages/core-components/src/components/Select/Select.tsx
index 1b57e0d4da..dbf5601112 100644
--- a/packages/core-components/src/components/Select/Select.tsx
+++ b/packages/core-components/src/components/Select/Select.tsx
@@ -16,7 +16,6 @@
import Box from '@material-ui/core/Box';
import Checkbox from '@material-ui/core/Checkbox';
import Chip from '@material-ui/core/Chip';
-import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import FormControl from '@material-ui/core/FormControl';
import InputBase from '@material-ui/core/InputBase';
import InputLabel from '@material-ui/core/InputLabel';
@@ -171,7 +170,7 @@ export function SelectComponent(props: SelectProps) {
onChange(event.target.value as SelectedItems);
};
- const handleClick = (event: React.ChangeEvent) => {
+ const handleOpen = (event: React.ChangeEvent) => {
if (disabled) {
event.preventDefault();
return;
@@ -184,7 +183,7 @@ export function SelectComponent(props: SelectProps) {
});
};
- const handleClickAway = () => {
+ const handleClose = () => {
setOpen(false);
};
@@ -196,88 +195,85 @@ export function SelectComponent(props: SelectProps) {
return (
-
-
- {label}
- }
- label={label}
- tabIndex={0}
- renderValue={s =>
- multiple && (value as any[]).length !== 0 ? (
-
- {(s as string[]).map(selectedValue => (
- el.value === selectedValue)?.value}
- label={
- items.find(el => el.value === selectedValue)?.label
- }
- clickable
- onDelete={handleDelete(selectedValue)}
- className={classes.chip}
- />
- ))}
-
- ) : (
-
- {(value as any[]).length === 0
- ? placeholder || ''
- : items.find(el => el.value === s)?.label}
-
- )
- }
- IconComponent={() =>
- !isOpen ? :
- }
- MenuProps={{
- anchorOrigin: {
- vertical: 'bottom',
- horizontal: 'left',
- },
- transformOrigin: {
- vertical: 'top',
- horizontal: 'left',
- },
- getContentAnchorEl: null,
- }}
- >
- {placeholder && !multiple && (
-
- )}
- {native
- ? items &&
- items.map(item => (
-
- ))
- : items &&
- items.map(item => (
-
+
+ {label}
+ }
+ label={label}
+ tabIndex={0}
+ renderValue={s =>
+ multiple && (value as any[]).length !== 0 ? (
+
+ {(s as string[]).map(selectedValue => (
+ el.value === selectedValue)?.value}
+ label={items.find(el => el.value === selectedValue)?.label}
+ clickable
+ onDelete={handleDelete(selectedValue)}
+ className={classes.chip}
+ />
))}
-
-
-
+
+ ) : (
+
+ {(value as any[]).length === 0
+ ? placeholder || ''
+ : items.find(el => el.value === s)?.label}
+
+ )
+ }
+ IconComponent={() =>
+ !isOpen ? :
+ }
+ MenuProps={{
+ anchorOrigin: {
+ vertical: 'bottom',
+ horizontal: 'left',
+ },
+ transformOrigin: {
+ vertical: 'top',
+ horizontal: 'left',
+ },
+ getContentAnchorEl: null,
+ }}
+ >
+ {placeholder && !multiple && (
+
+ )}
+ {native
+ ? items &&
+ items.map(item => (
+
+ ))
+ : items &&
+ items.map(item => (
+
+ ))}
+
+
);
}
diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx
index 85d9d97c7f..242bed6311 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { render, waitFor, screen } from '@testing-library/react';
+import { render, waitFor, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { CurveFilter } from './CurveFilter';
@@ -33,7 +33,9 @@ describe('', () => {
expect(screen.getByText('Step Before')).toBeInTheDocument();
- await userEvent.click(screen.getByTestId('select'));
+ await userEvent.click(
+ within(screen.getByTestId('select')).getByRole('button'),
+ );
await userEvent.click(screen.getByText('Monotone X'));
await waitFor(() => {
diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/DirectionFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/DirectionFilter.test.tsx
index cb660b5a25..dc74218316 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphPage/DirectionFilter.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphPage/DirectionFilter.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { render, waitFor, screen } from '@testing-library/react';
+import { render, waitFor, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Direction } from '../EntityRelationsGraph';
@@ -37,7 +37,9 @@ describe('', () => {
expect(screen.getByText('Right to left')).toBeInTheDocument();
- await userEvent.click(screen.getByTestId('select'));
+ await userEvent.click(
+ within(screen.getByTestId('select')).getByRole('button'),
+ );
await userEvent.click(screen.getByText('Top to bottom'));
await waitFor(() => {
diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
index 04d8df3d16..2513f8dca9 100644
--- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
@@ -19,7 +19,7 @@ import { Entity } from '@backstage/catalog-model';
import { ApiProvider } from '@backstage/core-app-api';
import { alertApiRef } from '@backstage/core-plugin-api';
import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
-import { fireEvent, waitFor, screen } from '@testing-library/react';
+import { fireEvent, waitFor, screen, within } from '@testing-library/react';
import { capitalize } from 'lodash';
import { default as React } from 'react';
import { catalogApiRef } from '../../api';
@@ -87,7 +87,7 @@ describe('', () => {
expect(screen.getByText('Kind')).toBeInTheDocument();
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
await waitFor(() => screen.getByText('Domain'));
@@ -115,7 +115,7 @@ describe('', () => {
,
);
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
await waitFor(() => screen.getByText('Domain'));
fireEvent.click(screen.getByText('Domain'));
@@ -171,7 +171,7 @@ describe('', () => {
);
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
expect(
screen.getByRole('option', { name: 'Component' }),
@@ -196,7 +196,7 @@ describe('', () => {
expect(screen.getByText('Frob')).toBeInTheDocument();
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
expect(screen.getByRole('option', { name: 'Domain' })).toBeInTheDocument();
});
});
diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
index d61a6c480f..3e69c35180 100644
--- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import { fireEvent, waitFor, screen } from '@testing-library/react';
+import { fireEvent, waitFor, screen, within } from '@testing-library/react';
import { Entity } from '@backstage/catalog-model';
import { EntityTypePicker } from './EntityTypePicker';
import { MockEntityListContextProvider } from '../../testUtils/providers';
@@ -95,7 +95,7 @@ describe('', () => {
expect(screen.getByText('Type')).toBeInTheDocument();
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
await waitFor(() => screen.getByText('service'));
@@ -119,7 +119,7 @@ describe('', () => {
,
);
const input = screen.getByTestId('select');
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
await waitFor(() => screen.getByText('service'));
fireEvent.click(screen.getByText('service'));
@@ -128,7 +128,7 @@ describe('', () => {
type: new EntityTypeFilter(['service']),
});
- fireEvent.click(input);
+ fireEvent.mouseDown(within(input).getByRole('button'));
fireEvent.click(screen.getByText('all'));
expect(updateFilters).toHaveBeenLastCalledWith({ type: undefined });
diff --git a/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx b/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
index 5062da4749..bc81b57882 100644
--- a/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
+++ b/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
@@ -18,7 +18,7 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { BuildListFilter } from './BuildListFilter';
import { BuildFilters, xcmetricsApiRef } from '../../api';
-import { RenderResult } from '@testing-library/react';
+import { RenderResult, within } from '@testing-library/react';
jest.mock('../../api/XcmetricsClient');
const client = require('../../api/XcmetricsClient');
@@ -50,13 +50,13 @@ const renderWithFiltersVisible = async (
const setStatusFilter = async (rendered: RenderResult, option: string) => {
const statusSelect = rendered.getAllByTestId('select')[0];
- await userEvent.click(statusSelect);
+ await userEvent.click(within(statusSelect).getByRole('button'));
await userEvent.click((await rendered.findAllByText(option))[0]);
};
const setProjectFilter = async (rendered: RenderResult, option: string) => {
const statusSelect = rendered.getAllByTestId('select')[1];
- await userEvent.click(statusSelect);
+ await userEvent.click(within(statusSelect).getByRole('button'));
const options = await rendered.findAllByText(option);
await userEvent.click(options[options.length - 1]);
};