Plugin Catalog: Fix A11y issues on plugin list (#40629)
This commit is contained in:
@@ -13,6 +13,7 @@ export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
name: IconName;
|
name: IconName;
|
||||||
size?: IconSize;
|
size?: IconSize;
|
||||||
type?: IconType;
|
type?: IconType;
|
||||||
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
@@ -44,7 +45,7 @@ function getIconSubDir(name: IconName, type: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
|
export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
|
||||||
({ size = 'md', type = 'default', name, className, style, ...divElementProps }, ref) => {
|
({ size = 'md', type = 'default', name, className, style, title = '', ...divElementProps }, ref) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
/* Temporary solution to display also font awesome icons */
|
/* Temporary solution to display also font awesome icons */
|
||||||
@@ -73,6 +74,7 @@ export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
|
|||||||
src={svgPath}
|
src={svgPath}
|
||||||
width={svgWid}
|
width={svgWid}
|
||||||
height={svgHgt}
|
height={svgHgt}
|
||||||
|
title={title}
|
||||||
className={cx(styles.icon, className, type === 'mono' ? { [styles.orange]: name === 'favorite' } : '')}
|
className={cx(styles.icon, className, type === 'mono' ? { [styles.orange]: name === 'favorite' } : '')}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,32 @@ import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/dat
|
|||||||
import { PluginListItem } from './PluginListItem';
|
import { PluginListItem } from './PluginListItem';
|
||||||
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
import { CatalogPlugin, PluginListDisplayMode } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The whole Icon component needs to be mock
|
||||||
|
* currently is using react-inlinesvg that does not render the icon svg in the test.
|
||||||
|
*
|
||||||
|
* There is solution to mock the library on __mocks__
|
||||||
|
* https://github.com/gilbarbara/react-inlinesvg/issues/145
|
||||||
|
* But unfortunately that causes conflict with DashboardSearch.test.tsx
|
||||||
|
*/
|
||||||
|
|
||||||
|
jest.mock('@grafana/ui', () => {
|
||||||
|
const IconMock = ({ title }: { title: string }) => {
|
||||||
|
return (
|
||||||
|
<svg>
|
||||||
|
<title> {title} </title>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
IconMock.displayName = 'Icon';
|
||||||
|
return Object.assign({}, jest.requireActual('@grafana/ui'), { Icon: IconMock });
|
||||||
|
});
|
||||||
|
|
||||||
describe('PluginListItem', () => {
|
describe('PluginListItem', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
const plugin: CatalogPlugin = {
|
const plugin: CatalogPlugin = {
|
||||||
description: 'The test plugin',
|
description: 'The test plugin',
|
||||||
downloads: 5,
|
downloads: 5,
|
||||||
@@ -49,21 +74,21 @@ describe('PluginListItem', () => {
|
|||||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||||
render(<PluginListItem plugin={datasourcePlugin} pathName="" />);
|
render(<PluginListItem plugin={datasourcePlugin} pathName="" />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/datasource plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/datasource plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a panel plugin with correct icon', () => {
|
it('renders a panel plugin with correct icon', () => {
|
||||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||||
render(<PluginListItem plugin={panelPlugin} pathName="" />);
|
render(<PluginListItem plugin={panelPlugin} pathName="" />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/panel plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/panel plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an app plugin with correct icon', () => {
|
it('renders an app plugin with correct icon', () => {
|
||||||
const appPlugin = { ...plugin, type: PluginType.app };
|
const appPlugin = { ...plugin, type: PluginType.app };
|
||||||
render(<PluginListItem plugin={appPlugin} pathName="" />);
|
render(<PluginListItem plugin={appPlugin} pathName="" />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/app plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/app plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||||
@@ -92,21 +117,21 @@ describe('PluginListItem', () => {
|
|||||||
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
const datasourcePlugin = { ...plugin, type: PluginType.datasource };
|
||||||
render(<PluginListItem plugin={datasourcePlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
render(<PluginListItem plugin={datasourcePlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/datasource plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/datasource plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a panel plugin with correct icon', () => {
|
it('renders a panel plugin with correct icon', () => {
|
||||||
const panelPlugin = { ...plugin, type: PluginType.panel };
|
const panelPlugin = { ...plugin, type: PluginType.panel };
|
||||||
render(<PluginListItem plugin={panelPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
render(<PluginListItem plugin={panelPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/panel plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/panel plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an app plugin with correct icon', () => {
|
it('renders an app plugin with correct icon', () => {
|
||||||
const appPlugin = { ...plugin, type: PluginType.app };
|
const appPlugin = { ...plugin, type: PluginType.app };
|
||||||
render(<PluginListItem plugin={appPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
render(<PluginListItem plugin={appPlugin} pathName="" displayMode={PluginListDisplayMode.List} />);
|
||||||
|
|
||||||
expect(screen.getByLabelText(/app plugin icon/i)).toBeVisible();
|
expect(screen.getByTitle(/app plugin/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a disabled plugin with a badge to indicate its error', () => {
|
it('renders a disabled plugin with a badge to indicate its error', () => {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function PluginListItem({ plugin, pathName, displayMode = PluginListDispl
|
|||||||
<PluginListItemBadges plugin={plugin} />
|
<PluginListItemBadges plugin={plugin} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.pluginType}>
|
<div className={styles.pluginType}>
|
||||||
{plugin.type && <Icon name={PluginIconName[plugin.type]} aria-label={`${plugin.type} plugin icon`} />}
|
{plugin.type && <Icon name={PluginIconName[plugin.type]} title={`${plugin.type} plugin`} />}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user