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
15 changes: 15 additions & 0 deletions .changeset/fix-account-modal-accessible-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@rainbow-me/rainbowkit": patch
---

fix: give the account modal an accessible name

`AccountModal` set `aria-labelledby="rk_account_modal_title"` on its dialog,
but `ProfileDetails` rendered its heading with its own hardcoded id,
`rk_profile_title`, and put that same id on the balance heading too. The
reference never resolved, so screen readers announced the modal as an
unnamed dialog, and the duplicated id was invalid on its own.

`ProfileDetails` now takes the `titleId` from `AccountModal` and renders it
on the account name only, so the dialog is announced by the connected
address or ENS name, the way the chain and connect modals already are.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import user from '@testing-library/user-event';
import React, { Fragment } from 'react';
import { describe, expect, it } from 'vitest';
import { useConnect } from 'wagmi';
import { renderWithProviders } from '../../../test/';
import { mockedAccounts } from '../../../test/mockWallet';
import { formatAddress } from '../ConnectButton/formatAddress';
import { AccountModal } from './AccountModal';

const AccountModalWithConnectButton = () => {
const { connect, connectors } = useConnect();

return (
<Fragment>
<AccountModal onClose={() => {}} open />
<button
type="button"
onClick={() => connect({ connector: connectors[0] })}
data-testid="rk-connect-btn"
>
connect
</button>
</Fragment>
);
};

describe('<AccountModal />', () => {
it('names the dialog after the connected account', async () => {
const modal = renderWithProviders(<AccountModalWithConnectButton />);

await user.click(await modal.findByTestId('rk-connect-btn'));

const dialog = await modal.findByRole('dialog');
const titleId = dialog.getAttribute('aria-labelledby');
expect(titleId).toBeTruthy();

// The id the dialog points at must exist exactly once, on the account name
const titles = document.querySelectorAll(`[id="${titleId}"]`);
expect(titles).toHaveLength(1);
expect(titles[0]).toHaveTextContent(formatAddress(mockedAccounts[0]));
expect(dialog).toHaveAccessibleName(formatAddress(mockedAccounts[0]));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function AccountModal({ onClose, open }: AccountModalProps) {
balance={balance}
onClose={onClose}
onDisconnect={disconnect}
titleId={titleId}
/>
</DialogContent>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface ProfileDetailsProps {
balance: ReturnType<typeof useProfile>['balance'];
onClose: () => void;
onDisconnect: () => void;
titleId: string;
}

export function ProfileDetails({
Expand All @@ -35,6 +36,7 @@ export function ProfileDetails({
balance,
onClose,
onDisconnect,
titleId,
}: ProfileDetailsProps) {
const showRecentTransactions = useContext(ShowRecentTransactionsContext);
const { i18n } = useContext(I18nContext);
Expand Down Expand Up @@ -65,7 +67,6 @@ export function ProfileDetails({
const displayBalance = ethBalance
? abbreviateETHBalance(Number.parseFloat(ethBalance))
: undefined;
const titleId = 'rk_profile_title';
const mobile = isMobile();

return (
Expand Down Expand Up @@ -120,7 +121,6 @@ export function ProfileDetails({
<Text
as="h1"
color="modalTextSecondary"
id={titleId}
size={mobile ? '16' : '14'}
weight="semibold"
>
Expand Down