diff --git a/frontend/components/CurrencyInput.test.tsx b/frontend/components/CurrencyInput.test.tsx index 81d1005..441e2e7 100644 --- a/frontend/components/CurrencyInput.test.tsx +++ b/frontend/components/CurrencyInput.test.tsx @@ -1,12 +1,15 @@ import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; +import { axe, toHaveNoViolations } from "jest-axe"; import { CurrencyInput } from "./CurrencyInput"; +expect.extend(toHaveNoViolations); + describe("CurrencyInput Component", () => { it("renders input prefixed with ₦ symbol and formats thousands separator", () => { render(); - const input = screen.getByRole("textbox") as HTMLInputElement; + const input = screen.getByRole("spinbutton") as HTMLInputElement; expect(input.value).toBe("50,000"); expect(screen.getByText("₦")).toBeInTheDocument(); }); @@ -15,7 +18,7 @@ describe("CurrencyInput Component", () => { const handleChange = jest.fn(); render(); - const input = screen.getByRole("textbox"); + const input = screen.getByRole("spinbutton"); fireEvent.change(input, { target: { value: "₦ 50,000abc" } }); expect(handleChange).toHaveBeenCalledWith(50000); @@ -25,7 +28,7 @@ describe("CurrencyInput Component", () => { const handleChange = jest.fn(); render(); - const input = screen.getByRole("textbox"); + const input = screen.getByRole("spinbutton"); fireEvent.paste(input, { clipboardData: { getData: () => "1,234.56abc" }, }); @@ -44,7 +47,7 @@ describe("CurrencyInput Component", () => { const handleChange = jest.fn(); render(); - const input = screen.getByRole("textbox"); + const input = screen.getByRole("spinbutton"); fireEvent.keyDown(input, { key: "ArrowUp" }); expect(handleChange).toHaveBeenCalledWith(600); @@ -52,6 +55,21 @@ describe("CurrencyInput Component", () => { fireEvent.keyDown(input, { key: "ArrowDown" }); expect(handleChange).toHaveBeenCalledWith(400); }); + + it("exposes its name, bounds, current value, and announced validation", async () => { + const { container } = render( + + ); + const input = screen.getByRole("spinbutton", { name: "Amount in Nigerian naira" }); + + expect(input).toHaveAttribute("aria-valuemin", "100"); + expect(input).toHaveAttribute("aria-valuemax", "500"); + expect(input).toHaveAttribute("aria-valuenow", "50"); + expect(input).toHaveAttribute("aria-valuetext", "₦50"); + expect(input).toHaveAttribute("aria-invalid", "true"); + expect(screen.getByRole("alert")).toHaveAttribute("aria-live", "assertive"); + expect(await axe(container)).toHaveNoViolations(); + }); }); describe("CurrencyInput available-amount caption (#447)", () => { diff --git a/frontend/components/CurrencyInput.tsx b/frontend/components/CurrencyInput.tsx index 1c5b3ae..291a621 100644 --- a/frontend/components/CurrencyInput.tsx +++ b/frontend/components/CurrencyInput.tsx @@ -116,6 +116,7 @@ export function CurrencyInput({ id={id} name={name} type="text" + role="spinbutton" inputMode="numeric" value={displayValue} onChange={handleChange} @@ -123,6 +124,11 @@ export function CurrencyInput({ onPaste={handlePaste} placeholder={placeholder} disabled={disabled} + aria-label={rest["aria-label"] ?? "Amount in Nigerian naira"} + aria-valuemin={min ?? 0} + aria-valuemax={max} + aria-valuenow={numericValue} + aria-valuetext={`₦${displayValue || "0"}`} aria-invalid={isBelowMin || Boolean(errorText) ? "true" : undefined} aria-describedby={ minErrorMessage || errorText ? `${id}-error` : rest["aria-describedby"] @@ -133,7 +139,7 @@ export function CurrencyInput({ {(minErrorMessage || errorText) && ( - )}