Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/MarketSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ export const MarketSwitcher = ({ hideDescription }: MarketSwitcherProps) => {
<IconButton
className="grid-fav-btn"
size="small"
aria-label={isFavorite ? t`Remove from Favourites` : t`Add to Favourites`}
onClick={(e) => handleStarClick(e, marketId)}
sx={{ padding: '1px', ml: 0.5, flexShrink: 0 }}
>
Expand Down
2 changes: 2 additions & 0 deletions src/components/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { XCircleIcon } from '@heroicons/react/solid';
import { t } from '@lingui/macro';
import { Box, BoxProps, IconButton, InputBase, useMediaQuery, useTheme } from '@mui/material';
import debounce from 'lodash/debounce';
import { useMemo, useRef, useState } from 'react';
Expand Down Expand Up @@ -75,6 +76,7 @@ export const SearchInput = ({
/>
<IconButton
sx={{ p: 0, mr: 2, visibility: searchTerm ? 'visible' : 'hidden' }}
aria-label={t`Clear search`}
onClick={() => handleClear()}
>
<XCircleIcon height={16} />
Expand Down
5 changes: 5 additions & 0 deletions src/components/TextWithTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InformationCircleIcon } from '@heroicons/react/outline';
import { t } from '@lingui/macro';
import { Box, BoxProps, IconButton, SvgIcon, Typography } from '@mui/material';
import { TypographyProps } from '@mui/material/Typography';
import { JSXElementConstructor, ReactElement, ReactNode, useState } from 'react';
Expand All @@ -13,6 +14,8 @@ export interface TextWithTooltipProps extends TypographyProps {
iconSize?: number;
iconColor?: string;
iconMargin?: number;
/** Accessible name for the icon button. Defaults to "More information". */
iconLabel?: string;
textColor?: string;
// eslint-disable-next-line
children?: ReactElement<any, string | JSXElementConstructor<any>>;
Expand All @@ -28,6 +31,7 @@ export const TextWithTooltip = ({
iconSize = 14,
iconColor,
iconMargin,
iconLabel,
children,
textColor,
wrapperProps: { sx: boxSx, ...boxRest } = {},
Expand Down Expand Up @@ -65,6 +69,7 @@ export const TextWithTooltip = ({
minWidth: 0,
ml: iconMargin || 0.5,
}}
aria-label={iconLabel ?? t`More information`}
onClick={() => {
if (event) {
trackEvent(event.eventName, { ...event.eventParams });
Expand Down
4 changes: 2 additions & 2 deletions src/components/TitleWithSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SearchIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
import { t, Trans } from '@lingui/macro';
import {
Box,
Button,
Expand Down Expand Up @@ -71,7 +71,7 @@ export const TitleWithSearchBar = <T extends React.ElementType>({
}}
>
{showSearchIcon && (
<IconButton onClick={() => setShowSearchBar(true)}>
<IconButton aria-label={t`Search`} onClick={() => setShowSearchBar(true)}>
<SvgIcon>
<SearchIcon />
</SvgIcon>
Expand Down
88 changes: 88 additions & 0 deletions src/components/__tests__/iconButtonLabels.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/** @jest-environment node */
import fs from 'fs';
import path from 'path';
import ts from 'typescript';

/**
* An icon-only <IconButton> has no text, so screen readers announce it as just "button".
* Every IconButton needs an `aria-label` / `aria-labelledby`, or must be the direct child of a
* MUI Tooltip with a plain string `title`, which MUI applies as `aria-label`.
*
* DarkTooltip does not count: it wraps `title` in a <Box> before handing it to MUI, and MUI only
* sets `aria-label` when `title` is a string, so the wrapped button stays unnamed.
*/

// Shared swap inputs are tracked separately in https://github.com/aave/interface/issues/3100.
// Exact `file:line` set: remove an entry when that button gets labelled. A new unlabelled
// button anywhere (including these files) fails the test; so does moving one of these, in
// which case update its line here.
const KNOWN_UNLABELLED = [
'src/components/transactions/Swap/inputs/LimitOrderInputs.tsx:145',
'src/components/transactions/Swap/inputs/LimitOrderInputs.tsx:184',
'src/components/transactions/Swap/inputs/MarketOrderInputs.tsx:113',
'src/components/transactions/Swap/inputs/MarketOrderInputs.tsx:145',
'src/components/transactions/Swap/inputs/primitives/SwapAssetInput.tsx:360',
];

const ROOT = path.resolve(__dirname, '../../..');

const listTsx = (dir: string): string[] =>
fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return entry.name === '__tests__' ? [] : listTsx(full);
return entry.name.endsWith('.tsx') ? [full] : [];
});

const hasAttr = (attrs: ts.JsxAttributes, names: string[]) =>
attrs.properties.some((p) => ts.isJsxAttribute(p) && names.includes(p.name.getText()));

const isNamedByTooltip = (node: ts.JsxOpeningLikeElement) => {
const element = ts.isJsxOpeningElement(node) ? node.parent : node;
const parent = element.parent;
if (!ts.isJsxElement(parent)) return false;
const opening = parent.openingElement;
if (opening.tagName.getText() !== 'Tooltip') return false;
return opening.attributes.properties.some(
(p) =>
ts.isJsxAttribute(p) &&
p.name.getText() === 'title' &&
!!p.initializer &&
ts.isStringLiteral(p.initializer)
);
};

const findUnlabelledIconButtons = (file: string) => {
const source = ts.createSourceFile(
file,
fs.readFileSync(file, 'utf8'),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX
);
const found: string[] = [];
const visit = (node: ts.Node) => {
if (
(ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) &&
node.tagName.getText() === 'IconButton' &&
!hasAttr(node.attributes, ['aria-label', 'aria-labelledby']) &&
!isNamedByTooltip(node)
) {
const { line } = source.getLineAndCharacterOfPosition(node.getStart());
found.push(`${path.relative(ROOT, file)}:${line + 1}`);
}
ts.forEachChild(node, visit);
};
visit(source);
return found;
};

describe('IconButton accessible names', () => {
it('labels every icon-only button', () => {
const unlabelled = [
...listTsx(path.join(ROOT, 'src')),
...listTsx(path.join(ROOT, 'pages')),
].flatMap(findUnlabelledIconButtons);

expect(unlabelled.sort()).toEqual([...KNOWN_UNLABELLED].sort());
});
});
8 changes: 7 additions & 1 deletion src/components/infoTooltips/TokenContractTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { t } from '@lingui/macro';
import { IconButton } from '@mui/material';
import { ArrowUpRightIcon } from 'src/components/icons/ArrowUpRightIcon';

Expand All @@ -6,7 +7,12 @@ import { DarkTooltip } from './DarkTooltip';

export const TokenContractTooltip = ({ explorerUrl }: { explorerUrl: string }) => (
<DarkTooltip title="View token contract" sx={{ display: { xsm: 'none' } }}>
<IconButton LinkComponent={Link} href={explorerUrl} sx={{ height: '24px', width: '24px' }}>
<IconButton
LinkComponent={Link}
href={explorerUrl}
aria-label={t`View token contract`}
sx={{ height: '24px', width: '24px' }}
>
<ArrowUpRightIcon sx={{ fontSize: '14px' }} />
</IconButton>
</DarkTooltip>
Expand Down
2 changes: 2 additions & 0 deletions src/components/primitives/BasicModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { t } from '@lingui/macro';
import { alpha, IconButton, Modal, Paper, PaperProps } from '@mui/material';
import React, { createContext, forwardRef, useContext, useLayoutEffect, useState } from 'react';

Expand Down Expand Up @@ -86,6 +87,7 @@ export const BasicModalSurface = forwardRef<HTMLDivElement, BasicModalSurfacePro
},
}}
onClick={onClose}
aria-label={t`Close`}
data-cy={'close-button'}
>
<CloseIcon data-cy={'CloseModalIcon'} sx={{ fontSize: '30px', color: 'fg-3' }} />
Expand Down
3 changes: 2 additions & 1 deletion src/components/transactions/AssetInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { XCircleIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
import { t, Trans } from '@lingui/macro';
import {
Box,
BoxProps,
Expand Down Expand Up @@ -187,6 +187,7 @@ export const AssetInput = <T extends Asset = Asset>({
color: 'fg-2',
},
}}
aria-label={t`Clear amount`}
onClick={() => {
onChange && onChange('');
}}
Expand Down
3 changes: 2 additions & 1 deletion src/components/transactions/Bridge/BridgeModalContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChainId } from '@aave/contract-helpers';
import { SwitchVerticalIcon } from '@heroicons/react/outline';
import { Trans } from '@lingui/macro';
import { t, Trans } from '@lingui/macro';
import {
Alert,
Box,
Expand Down Expand Up @@ -393,6 +393,7 @@ export const BridgeModalContent = () => {
defaultNetwork={sourceNetworkObj}
/>
<IconButton
aria-label={t`Switch source and destination networks`}
onClick={handleSwapNetworks}
sx={{
border: '1px solid',
Expand Down
2 changes: 1 addition & 1 deletion src/locales/el/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/locales/en/messages.js

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ msgstr "If you DO NOT unstake within {0} of unstake window, you will need to act
msgid "Reward APY at target liquidity"
msgstr "Reward APY at target liquidity"

#: src/components/infoTooltips/TokenContractTooltip.tsx
msgid "View token contract"
msgstr "View token contract"

#: src/layouts/SupportModal.tsx
msgid "Thank you for submitting your inquiry!"
msgstr "Thank you for submitting your inquiry!"
Expand Down Expand Up @@ -811,6 +815,10 @@ msgstr "This is a program initiated by the Aave DAO and implemented by Merkl. Aa
msgid "Choose one of the on-ramp services"
msgstr "Choose one of the on-ramp services"

#: src/components/TitleWithSearchBar.tsx
msgid "Search"
msgstr "Search"

#: src/modules/reserve-overview/BorrowInfo.tsx
msgid "Maximum amount available to borrow is <0/> {0} (<1/>)."
msgstr "Maximum amount available to borrow is <0/> {0} (<1/>)."
Expand Down Expand Up @@ -983,6 +991,10 @@ msgstr "Protocol Incentives"
msgid "Watch wallet"
msgstr "Watch wallet"

#: src/modules/bridge/BridgeTransactionListItem.tsx
msgid "View in explorer"
msgstr "View in explorer"

#: src/layouts/components/ThemeSwitcher/index.tsx
msgid "System"
msgstr "System"
Expand Down Expand Up @@ -1277,6 +1289,10 @@ msgstr "{selectedCount} Categories"
msgid "{tooltipText}"
msgstr "{tooltipText}"

#: src/components/MarketSwitcher.tsx
msgid "Remove from Favourites"
msgstr "Remove from Favourites"

#: src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx
#: src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaParaswapAdapters.tsx
msgid "Swapping {0} collateral"
Expand Down Expand Up @@ -1960,6 +1976,10 @@ msgstr "This action will reduce V2 health factor below liquidation threshold. re
msgid "{currentMethod}"
msgstr "{currentMethod}"

#: src/components/transactions/Bridge/BridgeModalContent.tsx
msgid "Switch source and destination networks"
msgstr "Switch source and destination networks"

#: src/modules/bridge/BridgeTransactionListItem.tsx
msgid "View TX"
msgstr "View TX"
Expand Down Expand Up @@ -2192,6 +2212,7 @@ msgid "Liquidation penalty"
msgstr "Liquidation penalty"

#: src/components/MarketSwitcher.tsx
#: src/modules/migration/MigrationMarketCard.tsx
msgid "Select market"
msgstr "Select market"

Expand Down Expand Up @@ -2317,6 +2338,10 @@ msgstr "I understand the liquidation risk and want to proceed"
msgid "I confirm the swap knowing that I could lose up to <0>{0}%</0> on this swap."
msgstr "I confirm the swap knowing that I could lose up to <0>{0}%</0> on this swap."

#: src/components/SearchInput.tsx
msgid "Clear search"
msgstr "Clear search"

#: src/modules/history/actions/ActionDetails.tsx
msgid "Covered debt"
msgstr "Covered debt"
Expand Down Expand Up @@ -3414,6 +3439,10 @@ msgstr "Deposit"
msgid "Supply cap is exceeded"
msgstr "Supply cap is exceeded"

#: src/components/TextWithTooltip.tsx
msgid "More information"
msgstr "More information"

#: src/components/transactions/Supply/CollateralOptionsSelector.tsx
msgid "{0} collateral would have 0% LTV without E-Mode. Disable {1} as collateral to use this option."
msgstr "{0} collateral would have 0% LTV without E-Mode. Disable {1} as collateral to use this option."
Expand Down Expand Up @@ -3480,6 +3509,10 @@ msgstr "English"
msgid "Learn more in our <0>FAQ guide</0>"
msgstr "Learn more in our <0>FAQ guide</0>"

#: src/components/transactions/AssetInput.tsx
msgid "Clear amount"
msgstr "Clear amount"

#: src/components/transactions/Warnings/CowLowerThanMarketWarning.tsx
msgid "The selected rate is lower than the market price. You might incur a loss if you proceed."
msgstr "The selected rate is lower than the market price. You might incur a loss if you proceed."
Expand Down Expand Up @@ -4503,10 +4536,12 @@ msgstr "In Wallet"
msgid "Payloads"
msgstr "Payloads"

#: src/components/MarketSwitcher.tsx
#: src/components/TopInfoPanel/PageTitle.tsx
msgid "Add to Favourites"
msgstr "Add to Favourites"

#: src/components/primitives/BasicModal.tsx
#: src/components/transactions/FlowCommons/Error.tsx
#: src/components/transactions/FlowCommons/PermissionView.tsx
msgid "Close"
Expand Down
2 changes: 1 addition & 1 deletion src/locales/es/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/locales/fr/messages.js

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/modules/bridge/BridgeTransactionListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChainId } from '@aave/contract-helpers';
import { ArrowNarrowRightIcon, XIcon } from '@heroicons/react/outline';
import { CheckIcon } from '@heroicons/react/solid';
import { Trans } from '@lingui/macro';
import { t, Trans } from '@lingui/macro';
import {
Box,
Button,
Expand Down Expand Up @@ -149,7 +149,11 @@ export const BridgeTransactionListItem = ({
</ListColumn>
<ListColumn align="right" maxWidth={95} minWidth={95}>
<DarkTooltip title="View in explorer" sx={{ display: { xsm: 'none' } }}>
<IconButton LinkComponent={Link} href={`https://ccip.chain.link/tx/${txHash}`}>
<IconButton
LinkComponent={Link}
href={`https://ccip.chain.link/tx/${txHash}`}
aria-label={t`View in explorer`}
>
<ArrowUpRightIcon sx={{ fontSize: '16px' }} />
</IconButton>
</DarkTooltip>
Expand Down
3 changes: 2 additions & 1 deletion src/modules/migration/MigrationMarketCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrowNarrowRightIcon } from '@heroicons/react/solid';
import { t } from '@lingui/macro';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp';
import {
Expand Down Expand Up @@ -89,7 +90,7 @@ export const MigrationMarketCard: FC<MigrationMarketCardProps> = ({
</Typography>
{selectableMarkets && setFromMarketData && (
<>
<IconButton onClick={handleClick} sx={{ ml: 'auto' }}>
<IconButton aria-label={t`Select market`} onClick={handleClick} sx={{ ml: 'auto' }}>
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
</IconButton>
<Menu
Expand Down