A11y: enable rule jsx-a11y/alt-text (#55832)

* Enable jsx-a11y/alt-text rule

* Fix errors

* Fix tests

* Enable jsx-a11y/alt-text rule after solving merge conflict

* Delete unused import

* Modify files according to the reviewer's comments

* Revert test changes and update snapshot

* tweaks to image alt names

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
Laura Fernández 2022-10-03 10:27:04 +02:00 committed by GitHub
parent 6856784134
commit fca252e7dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 32 additions and 21 deletions

View File

@ -67,7 +67,7 @@
// we should fix them one by one and mark them as errors // we should fix them one by one and mark them as errors
// once they're all fixed, we can remove them all and instead extend the strict preset // once they're all fixed, we can remove them all and instead extend the strict preset
// with "extends": ["plugin:jsx-a11y/strict"] // with "extends": ["plugin:jsx-a11y/strict"]
"jsx-a11y/alt-text": "off", "jsx-a11y/alt-text": "error",
"jsx-a11y/anchor-has-content": "error", "jsx-a11y/anchor-has-content": "error",
"jsx-a11y/anchor-is-valid": "off", "jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/aria-activedescendant-has-tabindex": "error", "jsx-a11y/aria-activedescendant-has-tabindex": "error",

View File

@ -17,7 +17,7 @@ export const SelectOption = (props: ExtendedOptionProps) => {
return ( return (
<components.Option {...props}> <components.Option {...props}>
<div className="gf-form-select-box__desc-option"> <div className="gf-form-select-box__desc-option">
{data.imgUrl && <img className="gf-form-select-box__desc-option__img" src={data.imgUrl} />} {data.imgUrl && <img className="gf-form-select-box__desc-option__img" src={data.imgUrl} alt="" />}
<div className="gf-form-select-box__desc-option__body"> <div className="gf-form-select-box__desc-option__body">
<div>{children}</div> <div>{children}</div>
{data.description && <div className="gf-form-select-box__desc-option__desc">{data.description}</div>} {data.description && <div className="gf-form-select-box__desc-option__desc">{data.description}</div>}

View File

@ -14,6 +14,7 @@ exports[`SelectOption renders correctly 1`] = `
className="gf-form-select-box__desc-option" className="gf-form-select-box__desc-option"
> >
<img <img
alt=""
className="gf-form-select-box__desc-option__img" className="gf-form-select-box__desc-option__img"
src="url/to/avatar" src="url/to/avatar"
/> />

View File

@ -15,13 +15,13 @@ export const ImageCell: FC<TableCellProps> = (props) => {
return ( return (
<div {...cellProps} className={tableStyles.cellContainer}> <div {...cellProps} className={tableStyles.cellContainer}>
{!hasLinks && <img src={displayValue.text} className={tableStyles.imageCell} />} {!hasLinks && <img src={displayValue.text} className={tableStyles.imageCell} alt="" />}
{hasLinks && ( {hasLinks && (
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}> <DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
{(api) => { {(api) => {
return ( return (
<div onClick={api.openMenu} className={cx(tableStyles.imageCellLink, api.targetClassName)}> <div onClick={api.openMenu} className={cx(tableStyles.imageCellLink, api.targetClassName)}>
<img src={displayValue.text} className={tableStyles.imageCell} /> <img src={displayValue.text} className={tableStyles.imageCell} alt="" />
</div> </div>
); );
}} }}

View File

@ -291,7 +291,7 @@ func signRPMRepo(repoRoot string, cfg PublishConfig) error {
PrimaryKey: pubKey, PrimaryKey: pubKey,
PrivateKey: privKey, PrivateKey: privKey,
Identities: map[string]*openpgp.Identity{ Identities: map[string]*openpgp.Identity{
uid.Id: &openpgp.Identity{ uid.Id: {
Name: uid.Name, Name: uid.Name,
UserId: uid, UserId: uid,
SelfSignature: &packet.Signature{ SelfSignature: &packet.Signature{

View File

@ -48,7 +48,7 @@ export function TopSearchBar() {
{profileNode && ( {profileNode && (
<Dropdown overlay={<TopNavBarMenu node={profileNode} />}> <Dropdown overlay={<TopNavBarMenu node={profileNode} />}>
<button className={styles.actionItem}> <button className={styles.actionItem}>
<img src={contextSrv.user.gravatarUrl} /> <img src={contextSrv.user.gravatarUrl} alt="User avatar" />
</button> </button>
</Dropdown> </Dropdown>
)} )}

View File

@ -11,10 +11,10 @@ const setClassNameHelper = (inherited: boolean) => {
function ItemAvatar({ item }: { item: DashboardAcl }) { function ItemAvatar({ item }: { item: DashboardAcl }) {
if (item.userAvatarUrl) { if (item.userAvatarUrl) {
return <img className="filter-table__avatar" src={item.userAvatarUrl} />; return <img className="filter-table__avatar" src={item.userAvatarUrl} alt="User avatar" />;
} }
if (item.teamAvatarUrl) { if (item.teamAvatarUrl) {
return <img className="filter-table__avatar" src={item.teamAvatarUrl} />; return <img className="filter-table__avatar" src={item.teamAvatarUrl} alt="Team avatar" />;
} }
if (item.role === 'Editor') { if (item.role === 'Editor') {
return <Icon size="lg" name="edit" />; return <Icon size="lg" name="edit" />;

View File

@ -32,7 +32,7 @@ const RuleType: FC<Props> = (props) => {
return ( return (
<Card className={cardStyles} isSelected={selected} onClick={() => onClick(value)} disabled={disabled}> <Card className={cardStyles} isSelected={selected} onClick={() => onClick(value)} disabled={disabled}>
<Card.Figure> <Card.Figure>
<img src={image} /> <img src={image} alt="" />
</Card.Figure> </Card.Figure>
<Card.Heading>{name}</Card.Heading> <Card.Heading>{name}</Card.Heading>
<Card.Description>{description}</Card.Description> <Card.Description>{description}</Card.Description>

View File

@ -184,7 +184,7 @@ const DataSourceCell = memo(
return ( return (
<span className={styles.root}> <span className={styles.root}>
<img src={value.meta.info.logos.small} className={styles.dsLogo} /> <img src={value.meta.info.logos.small} alt="" className={styles.dsLogo} />
{value.name} {value.name}
</span> </span>
); );

View File

@ -70,10 +70,10 @@ export class PreviewSettings extends PureComponent<Props, State> {
</tr> </tr>
<tr> <tr>
<td> <td>
<img src={getThumbnailURL(uid, false)} style={imgstyle} /> <img src={getThumbnailURL(uid, false)} alt="Preview of dashboard in dark theme" style={imgstyle} />
</td> </td>
<td> <td>
<img src={getThumbnailURL(uid, true)} style={imgstyle} /> <img src={getThumbnailURL(uid, true)} alt="Preview of dashboard in light theme" style={imgstyle} />
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -11,7 +11,7 @@ export const PubdashFooter = function () {
<div className={styles.footer}> <div className={styles.footer}>
<span className={styles.logoText}> <span className={styles.logoText}>
<a href="https://grafana.com/" target="_blank" rel="noreferrer noopener"> <a href="https://grafana.com/" target="_blank" rel="noreferrer noopener">
powered by Grafana <img className={styles.logoImg} src="public/img/grafana_icon.svg"></img> powered by Grafana <img className={styles.logoImg} alt="" src="public/img/grafana_icon.svg"></img>
</a> </a>
</span> </span>
</div> </div>

View File

@ -37,7 +37,7 @@ export const FileUploader = ({ mediaType, setFormData, setUpload, error }: Props
<Field label="Preview"> <Field label="Preview">
<div className={styles.iconPreview}> <div className={styles.iconPreview}>
{mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />} {mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />}
{mediaType === MediaType.Image && <img src={file} className={styles.img} />} {mediaType === MediaType.Image && <img src={file} alt="Preview of the uploaded file" className={styles.img} />}
</div> </div>
</Field> </Field>
); );

View File

@ -40,7 +40,7 @@ function Cell(props: CellProps) {
{card.imgUrl.endsWith('.svg') ? ( {card.imgUrl.endsWith('.svg') ? (
<SVG src={card.imgUrl} className={styles.img} /> <SVG src={card.imgUrl} className={styles.img} />
) : ( ) : (
<img src={card.imgUrl} className={styles.img} /> <img src={card.imgUrl} alt="" className={styles.img} />
)} )}
<h6 className={styles.text}>{card.label.slice(0, -4)}</h6> <h6 className={styles.text}>{card.label.slice(0, -4)}</h6>
</div> </div>

View File

@ -34,7 +34,9 @@ export const URLPickerTab = (props: Props) => {
<Field label="Preview"> <Field label="Preview">
<div className={styles.iconPreview}> <div className={styles.iconPreview}>
{mediaType === MediaType.Icon && <SVG src={imgSrc} className={styles.img} />} {mediaType === MediaType.Icon && <SVG src={imgSrc} className={styles.img} />}
{mediaType === MediaType.Image && newValue && <img src={imgSrc} className={styles.img} />} {mediaType === MediaType.Image && newValue && (
<img src={imgSrc} alt="Preview of the selected URL" className={styles.img} />
)}
</div> </div>
</Field> </Field>
<Label>{shortName}</Label> <Label>{shortName}</Label>

View File

@ -130,7 +130,13 @@ export function SearchCard({ editable, item, onTagSelected, onToggleChecked, onC
onClick={onCheckboxClick} onClick={onCheckboxClick}
/> />
{hasImage ? ( {hasImage ? (
<img loading="lazy" className={styles.image} src={imageSrc} onError={() => setHasImage(false)} /> <img
loading="lazy"
className={styles.image}
src={imageSrc}
alt="Dashboard preview"
onError={() => setHasImage(false)}
/>
) : ( ) : (
<div className={styles.imagePlaceholder}> <div className={styles.imagePlaceholder}>
{item.icon ? ( {item.icon ? (

View File

@ -33,6 +33,7 @@ export function SearchCardExpanded({ className, imageHeight, imageWidth, item, l
{hasImage ? ( {hasImage ? (
<img <img
loading="lazy" loading="lazy"
alt="Dashboard preview"
className={styles.image} className={styles.image}
src={imageSrc} src={imageSrc}
onLoad={() => setHasImage(true)} onLoad={() => setHasImage(true)}

View File

@ -302,7 +302,7 @@ function makeDataSourceColumn(
onDatasourceChange(settings.uid); onDatasourceChange(settings.uid);
}} }}
> >
<img src={icon} width={14} height={14} title={settings.type} className={iconClass} /> <img src={icon} alt="" width={14} height={14} title={settings.type} className={iconClass} />
{settings.name} {settings.name}
</span> </span>
); );

View File

@ -62,7 +62,7 @@ export function FileView({ listing, path, onPathChange, view }: Props) {
return ( return (
<div> <div>
<a target={'_self'} href={src}> <a target={'_self'} href={src}>
<img src={src} className={styles.img} /> <img src={src} alt="File preview" className={styles.img} />
</a> </a>
</div> </div>
); );

View File

@ -168,7 +168,7 @@ export function DashboardQueryEditor({ panelData, queries, onChange, onRunQuerie
{results.map((target, i) => ( {results.map((target, i) => (
<div className={styles.queryEditorRowHeader} key={`DashboardQueryRow-${i}`}> <div className={styles.queryEditorRowHeader} key={`DashboardQueryRow-${i}`}>
<div> <div>
<img src={target.img} width={16} /> <img src={target.img} alt="" width={16} />
<span className={styles.refId}>{target.refId}:</span> <span className={styles.refId}>{target.refId}:</span>
</div> </div>
<div> <div>

View File

@ -923,6 +923,7 @@ export class PrometheusDatasource
<img <img
style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }} style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }}
src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]} src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]}
alt=""
/>{' '} />{' '}
{buildInfo.application ? AppDisplayNames[buildInfo.application] : 'Unknown'} {buildInfo.application ? AppDisplayNames[buildInfo.application] : 'Unknown'}
</span> </span>

View File

@ -37,7 +37,7 @@ export const AnnotationTooltip = ({
const ts = <span className={styles.time}>{Boolean(annotation.isRegion) ? `${time} - ${timeEnd}` : time}</span>; const ts = <span className={styles.time}>{Boolean(annotation.isRegion) ? `${time} - ${timeEnd}` : time}</span>;
if (annotation.login && annotation.avatarUrl) { if (annotation.login && annotation.avatarUrl) {
avatar = <img className={styles.avatar} src={annotation.avatarUrl} />; avatar = <img className={styles.avatar} alt="Annotation avatar" src={annotation.avatarUrl} />;
} }
if (annotation.alertId !== undefined && annotation.newState) { if (annotation.alertId !== undefined && annotation.newState) {