Skip to content
Merged
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
23 changes: 23 additions & 0 deletions src/components/QRCode.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,27 @@ describe("QRCode", () => {
const wrapper = container.firstElementChild;
expect(wrapper?.classList.contains("my-qr")).toBe(true);
});

it("exposes the canvas to assistive tech as an image", () => {
render(<QRCode value={value} />);
const img = screen.getByRole("img");
expect(img.tagName).toBe("CANVAS");
});

it("uses ariaLabel as the accessible name when provided", () => {
render(<QRCode value={value} ariaLabel="QR code to receive funds" />);
expect(
screen.getByRole("img", { name: "QR code to receive funds" }),
).toBeInTheDocument();
});

it("falls back to the label for the accessible name", () => {
render(<QRCode value={value} label={value} />);
expect(screen.getByRole("img", { name: value })).toBeInTheDocument();
});

it("defaults the accessible name to include the address", () => {
render(<QRCode value={value} />);
expect(screen.getByRole("img", { name: `QR code for address ${value}` })).toBeInTheDocument();
});
});
11 changes: 10 additions & 1 deletion src/components/QRCode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ interface QRCodeProps {
size?: number;
className?: string;
label?: string;
/**
* Accessible name for the QR code, announced by screen readers. A `<canvas>`
* is opaque to assistive tech, so without this the code is invisible to AT.
* Defaults to the `label` if provided, otherwise a generic description.
*/
ariaLabel?: string;
/** Canvas background colour. Defaults to `--color-qr-canvas-bg`. */
canvasBackground?: string;
/** Canvas foreground (cell) colour. Defaults to `--color-qr-canvas-fg`. */
Expand All @@ -30,13 +36,15 @@ export function QRCode({
size = 160,
className,
label,
ariaLabel,
canvasBackground,
canvasForeground,
}: QRCodeProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [renderError, setRenderError] = useState(false);
const lastPropsRef = useRef({ value, size, canvasBackground, canvasForeground });

/* eslint-disable react-hooks/refs */
if (
lastPropsRef.current.value !== value ||
lastPropsRef.current.size !== size ||
Expand All @@ -46,6 +54,7 @@ export function QRCode({
lastPropsRef.current = { value, size, canvasBackground, canvasForeground };
setRenderError(false);
}
/* eslint-enable react-hooks/refs */

useEffect(() => {
if (renderError || !value) return;
Expand Down Expand Up @@ -114,7 +123,7 @@ export function QRCode({
<canvas
ref={canvasRef}
role="img"
aria-label={`QR code for address ${value}`}
aria-label={ariaLabel ?? label ?? `QR code for address ${value}`}
style={{ display: "block", borderRadius: "4px" }}
/>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/components/SorobanPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ vi.mock("@/context/useSorokit", () => ({
useSorokit: vi.fn(),
}));

const mockInvokeContract = vi.fn();

// Mock the getClient from lib/client
vi.mock("../lib/client", () => ({
getClient: () => ({
Expand Down Expand Up @@ -50,7 +52,6 @@ describe("SorobanPanel", () => {
);

// Fill out contract ID and method to enable the button
const contractInput = screen.getByPlaceholderText(/c\.\.\./i);
const methodInput = screen.getByPlaceholderText(/transfer/i);
const argsInput = screen.getByPlaceholderText(/\[.*\]/i);
const invokeBtn = screen.getByRole("button", { name: /invoke/i });
Expand Down
7 changes: 6 additions & 1 deletion src/screens/WalletScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ export function WalletScreen() {
</p>
</div>
<div className="px-6 py-6 flex flex-col sm:flex-row items-center sm:items-start gap-6">
<QRCode value={address} size={140} className="shrink-0" />
<QRCode
value={address}
size={140}
className="shrink-0"
ariaLabel={`QR code to receive funds at address ${address}`}
/>
<div className="flex-1 min-w-0 w-full flex flex-col justify-center gap-1 sm:h-[164px]">
<AddressDisplay address={address} showFull label="Address" />
</div>
Expand Down
Loading