Accessibility: enable rule jsx-a11y/label-has-associated-control (#57964)

* use labels correctly

* fix opentsdb labels

* fix unit tests

* use aria-label instead of data-testid
This commit is contained in:
Ashley Harrison 2022-11-01 14:37:58 +00:00 committed by GitHub
parent 3770f4f2b7
commit e5c68f40c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 40 additions and 91 deletions

View File

@ -73,7 +73,6 @@
// we should remove the corresponding line and fix them one by one
// any marked "error" contain specific overrides we'll need to keep
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/no-autofocus": [
"error",

View File

@ -1,8 +1,7 @@
import { css } from '@emotion/css';
import React, { FunctionComponent, PureComponent } from 'react';
import React, { FunctionComponent } from 'react';
import { withTheme2, useStyles2 } from '../../themes';
import { Button } from '../Button';
import { Switch } from '../Forms/Legacy/Switch/Switch';
import { PopoverContentProps } from '../Tooltip';
@ -43,52 +42,6 @@ export const SeriesColorPickerPopover: FunctionComponent<SeriesColorPickerPopove
return <ColorPickerPopover {...colorPickerProps} color={color || '#000000'} customPickers={customPickers} />;
};
interface AxisSelectorProps {
yaxis: number;
onToggleAxis?: () => void;
}
interface AxisSelectorState {
yaxis: number;
}
export class AxisSelector extends PureComponent<AxisSelectorProps, AxisSelectorState> {
constructor(props: AxisSelectorProps) {
super(props);
this.state = {
yaxis: this.props.yaxis,
};
this.onToggleAxis = this.onToggleAxis.bind(this);
}
onToggleAxis() {
this.setState({
yaxis: this.state.yaxis === 2 ? 1 : 2,
});
if (this.props.onToggleAxis) {
this.props.onToggleAxis();
}
}
render() {
const leftButtonVariant = this.state.yaxis === 1 ? 'primary' : 'secondary';
const rightButtonVariant = this.state.yaxis === 2 ? 'primary' : 'secondary';
return (
<div className="p-b-1">
<label className="small p-r-1">Y Axis:</label>
<Button onClick={this.onToggleAxis} size="sm" variant={leftButtonVariant}>
Left
</Button>
<Button onClick={this.onToggleAxis} size="sm" variant={rightButtonVariant}>
Right
</Button>
</div>
);
}
}
// This component is to enable SeriesColorPickerPopover usage via series-color-picker-popover directive
export const SeriesColorPickerPopoverWithTheme = withTheme2(SeriesColorPickerPopover);

View File

@ -155,13 +155,14 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
<FormField label="Access" labelWidth={13} inputWidth={20} inputEl={accessSelect} />
</div>
<div className="gf-form">
<label
<button
type="button"
className="gf-form-label query-keyword pointer"
onClick={() => setIsAccessHelpVisible((isVisible) => !isVisible)}
>
Help&nbsp;
<Icon name={isAccessHelpVisible ? 'angle-down' : 'angle-right'} style={{ marginBottom: 0 }} />
</label>
</button>
</div>
</div>
{isAccessHelpVisible && <HttpAccessHelp />}

View File

@ -14,7 +14,7 @@ export interface Props extends Omit<HTMLProps<HTMLInputElement>, 'value'> {
}
export const Switch = React.forwardRef<HTMLInputElement, Props>(
({ value, checked, disabled, onChange, id, ...inputProps }, ref) => {
({ value, checked, disabled, onChange, id, label, ...inputProps }, ref) => {
if (checked) {
deprecationWarning('Switch', 'checked prop', 'value');
}
@ -36,7 +36,7 @@ export const Switch = React.forwardRef<HTMLInputElement, Props>(
{...inputProps}
ref={ref}
/>
<label htmlFor={switchIdRef.current} />
<label htmlFor={switchIdRef.current} aria-label={label ?? 'Toggle switch'} />
</div>
);
}

View File

@ -96,7 +96,7 @@ export const SliderValueEditor: React.FC<FieldConfigEditorProps<number, SliderFi
<div className={cx(styles.container, styles.slider)}>
{/** Slider tooltip's parent component is body and therefore we need Global component to do css overrides for it. */}
<Global styles={styles.slider} />
<label className={cx(styles.sliderInput, ...sliderInputClassNames)}>
<div className={cx(styles.sliderInput, ...sliderInputClassNames)}>
<SliderWithTooltip
min={min}
max={max}
@ -113,7 +113,7 @@ export const SliderValueEditor: React.FC<FieldConfigEditorProps<number, SliderFi
<span className={stylesSlider.numberInputWrapper} ref={inputRef}>
<NumberInput value={sliderValue} onChange={onSliderInputChange} max={max} min={min} step={step} />
</span>
</label>
</div>
</div>
);
};

View File

@ -22,6 +22,6 @@ describe('<BasicSettings>', () => {
setup();
expect(screen.getByRole('textbox', { name: selectors.pages.DataSource.name })).toBeInTheDocument();
expect(screen.getByRole('checkbox', { name: 'Default' })).toBeInTheDocument();
expect(screen.getByRole('checkbox', { name: /Default/ })).toBeInTheDocument();
});
});

View File

@ -10,10 +10,10 @@ export type Props = {
export function DataSourcePluginState({ state }: Props) {
return (
<div className="gf-form">
<label className="gf-form-label width-10">Plugin state</label>
<label className="gf-form-label gf-form-label--transparent">
<div className="gf-form-label width-10">Plugin state</div>
<div className="gf-form-label gf-form-label--transparent">
<PluginStateInfo state={state} />
</label>
</div>
</div>
);
}

View File

@ -48,7 +48,7 @@ export const switchToQueryHistoryTab = async (
export const selectStarredTabFirst = async (exploreId: ExploreId = ExploreId.left) => {
const checkbox = withinExplore(exploreId).getByRole('checkbox', {
name: 'Change the default active tab from “Query history” to “Starred”',
name: /Change the default active tab from “Query history” to “Starred”/,
});
await userEvent.click(checkbox);
};

View File

@ -94,10 +94,13 @@ export class FolderSettingsPage extends PureComponent<Props, State> {
<div className="section gf-form-group">
<form name="folderSettingsForm" onSubmit={this.onSave}>
<div className="gf-form">
<label className="gf-form-label width-7">Name</label>
<label htmlFor="folder-title" className="gf-form-label width-7">
Name
</label>
<Input
type="text"
className="gf-form-input width-30"
id="folder-title"
value={folder.title}
onChange={this.onTitleChange}
/>

View File

@ -18,7 +18,7 @@ export function Preview({ rawSql }: PreviewProps) {
const labelElement = (
<div className={styles.labelWrapper}>
<label className={styles.label}>Preview</label>
<span className={styles.label}>Preview</span>
<IconButton tooltip="Copy to clipboard" onClick={() => copyToClipboard(rawSql)} name="copy" />
</div>
);

View File

@ -19,7 +19,7 @@ export const AnnotationsHelp = () => {
<p>
Example Result: <code>monitoring.googleapis.com/uptime_check/http_status has this value: 502</code>
</p>
<label>Patterns:</label>
<span>Patterns:</span>
<p>
<code>{`${'{{metric.value}}'}`}</code> = value of the metric/point
</p>

View File

@ -24,7 +24,7 @@ export default class CloudMonitoringCheatSheet extends PureComponent<
Result: &nbsp;&nbsp;<code>cpu/usage_time - server1-europe-west-1</code>
<br />
<br />
<label>Patterns</label>
<span>Patterns:</span>
<br />
<ul
className={css`

View File

@ -53,7 +53,7 @@ describe('FilterSection', () => {
describe('filter editor', () => {
it('open the editor on clicking +', () => {
setup();
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add filter/ }));
expect(screen.getByText('Group by')).toBeInTheDocument();
});
@ -64,7 +64,7 @@ describe('FilterSection', () => {
it('should call runQuery on adding a filter', () => {
setup();
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add filter/ }));
fireEvent.click(screen.getByText('add filter'));
expect(onRunQuery).toHaveBeenCalled();
});
@ -78,7 +78,7 @@ describe('FilterSection', () => {
tags: [{}],
};
setup({ query });
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add filter/ }));
fireEvent.click(screen.getByText('add filter'));
expect(screen.getByTestId(testIds.error)).toBeInTheDocument();
});

View File

@ -138,11 +138,9 @@ export function FilterSection({
);
})}
{!addFilterMode && (
<label className="gf-form-label query-keyword">
<button type="button" className={buttonStyles} onClick={changeAddFilterMode} data-testid={testIds.open}>
<Icon name={'plus'} />
</button>
</label>
<button className="gf-form-label" type="button" onClick={changeAddFilterMode} aria-label="Add filter">
<Icon name={'plus'} />
</button>
)}
</div>
{addFilterMode && (
@ -225,19 +223,18 @@ export function FilterSection({
/>
<div className="gf-form">
{errors && (
<label className="gf-form-label" title={errors} data-testid={testIds.error}>
<div className="gf-form-label" title={errors} data-testid={testIds.error}>
<Icon name={'exclamation-triangle'} color={'rgb(229, 189, 28)'} />
</label>
</div>
)}
<label className="gf-form-label">
<div className="gf-form-label">
<button type="button" className={buttonStyles} onClick={addFilter}>
add filter
</button>
<button type="button" className={buttonStyles} onClick={changeAddFilterMode}>
<Icon name={'times'} />
</button>
</label>
</div>
</div>
</div>
)}
@ -250,7 +247,6 @@ export function FilterSection({
export const testIds = {
section: 'opentsdb-filter',
open: 'opentsdb-filter-editor',
list: 'opentsdb-filter-list',
error: 'opentsdb-filter-error',
remove: 'opentsdb-filter-remove',

View File

@ -48,7 +48,7 @@ describe('Tag Section', () => {
describe('tag editor', () => {
it('open the editor on clicking +', () => {
setup();
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add tag/ }));
expect(screen.getByText('add tag')).toBeInTheDocument();
});
@ -59,7 +59,7 @@ describe('Tag Section', () => {
it('should call runQuery on adding a tag', () => {
setup();
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add tag/ }));
fireEvent.click(screen.getByText('add tag'));
expect(onRunQuery).toHaveBeenCalled();
});
@ -80,7 +80,7 @@ describe('Tag Section', () => {
],
};
setup({ query });
fireEvent.click(screen.getByTestId(testIds.open));
fireEvent.click(screen.getByRole('button', { name: /Add tag/ }));
fireEvent.click(screen.getByText('add tag'));
expect(screen.getByTestId(testIds.error)).toBeInTheDocument();
});

View File

@ -136,11 +136,9 @@ export function TagSection({
);
})}
{!addTagMode && (
<label className="gf-form-label query-keyword">
<button type="button" className={buttonStyles} onClick={changeAddTagMode} data-testid={testIds.open}>
<Icon name={'plus'} />
</button>
</label>
<button className="gf-form-label" type="button" onClick={changeAddTagMode} aria-label="Add tag">
<Icon name={'plus'} />
</button>
)}
</div>
{addTagMode && (
@ -196,19 +194,19 @@ export function TagSection({
<div className="gf-form">
{errors && (
<label className="gf-form-label" title={errors} data-testid={testIds.error}>
<div className="gf-form-label" title={errors} data-testid={testIds.error}>
<Icon name={'exclamation-triangle'} color={'rgb(229, 189, 28)'} />
</label>
</div>
)}
<label className="gf-form-label">
<div className="gf-form-label">
<button type="button" className={buttonStyles} onClick={addTag}>
add tag
</button>
<button type="button" className={buttonStyles} onClick={changeAddTagMode}>
<Icon name={'times'} />
</button>
</label>
</div>
</div>
</div>
)}
@ -221,7 +219,6 @@ export function TagSection({
export const testIds = {
section: 'opentsdb-tag',
open: 'opentsdb-tag-editor',
list: 'opentsdb-tag-list',
error: 'opentsdb-tag-error',
remove: 'opentsdb-tag-remove',