import React from "react"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi } from "vitest"; import RadioGroup from "../../../app/components/RadioGroup"; describe("RadioGroup Accessibility", () => { const defaultOptions = [ { value: "option1", label: "Option 1" }, { value: "option2", label: "Option 2" }, { value: "option3", label: "Option 3" }, ]; it("has proper radiogroup role", () => { render(); const radioGroup = screen.getByRole("radiogroup"); expect(radioGroup).toBeInTheDocument(); }); it("has proper ARIA attributes on radiogroup", () => { render( ); const radioGroup = screen.getByRole("radiogroup"); expect(radioGroup).toHaveAttribute("aria-label", "Test Radio Group"); }); it("has proper radio button roles", () => { render(); const radioButtons = screen.getAllByRole("radio"); expect(radioButtons).toHaveLength(3); radioButtons.forEach((button) => { expect(button).toHaveAttribute("role", "radio"); expect(button).toHaveAttribute("aria-checked"); }); }); it("shows correct selection state", () => { render(); const radioButtons = screen.getAllByRole("radio"); expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); expect(radioButtons[1]).toHaveAttribute("aria-checked", "true"); expect(radioButtons[2]).toHaveAttribute("aria-checked", "false"); }); it("updates selection state correctly", () => { const { rerender } = render( ); let radioButtons = screen.getAllByRole("radio"); expect(radioButtons[0]).toHaveAttribute("aria-checked", "true"); rerender(); radioButtons = screen.getAllByRole("radio"); expect(radioButtons[0]).toHaveAttribute("aria-checked", "false"); expect(radioButtons[2]).toHaveAttribute("aria-checked", "true"); }); it("associates labels with radio buttons", () => { render(); const radioButtons = screen.getAllByRole("radio"); radioButtons.forEach((button, index) => { const labelId = button.getAttribute("aria-labelledby"); expect(labelId).toBeTruthy(); const labelElement = document.getElementById(labelId); expect(labelElement).toHaveTextContent(`Option ${index + 1}`); }); }); it("uses aria-label when provided in options", () => { const optionsWithAria = [ { value: "option1", label: "Option 1", ariaLabel: "First Option" }, { value: "option2", label: "Option 2", ariaLabel: "Second Option" }, ]; render(); const radioButtons = screen.getAllByRole("radio"); expect(radioButtons[0]).toHaveAttribute("aria-label", "First Option"); expect(radioButtons[1]).toHaveAttribute("aria-label", "Second Option"); }); it("is keyboard accessible", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); render( ); const radioButtons = screen.getAllByRole("radio"); // Focus first radio button radioButtons[0].focus(); expect(radioButtons[0]).toHaveFocus(); // Navigate to second option radioButtons[1].focus(); expect(radioButtons[1]).toHaveFocus(); // Activate with Space await user.keyboard(" "); expect(handleChange).toHaveBeenCalledWith({ value: "option2" }); }); it("handles Enter key activation", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); render( ); const radioButtons = screen.getAllByRole("radio"); await user.click(radioButtons[2]); // Focus the element first await user.keyboard("Enter"); expect(handleChange).toHaveBeenCalledWith({ value: "option3" }); }); it("handles Space key activation", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); render( ); const radioButtons = screen.getAllByRole("radio"); radioButtons[1].focus(); await user.keyboard(" "); expect(handleChange).toHaveBeenCalledWith({ value: "option2" }); }); it("ignores other keys", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); render( ); const radioButtons = screen.getAllByRole("radio"); radioButtons[1].focus(); await user.keyboard("a"); await user.keyboard("Tab"); await user.keyboard("Escape"); expect(handleChange).not.toHaveBeenCalled(); }); it("has proper tab order", () => { render(); const radioButtons = screen.getAllByRole("radio"); radioButtons.forEach((button) => { expect(button).toHaveAttribute("tabIndex", "0"); }); }); it("generates unique IDs for accessibility", () => { render(
); const radioButtons = screen.getAllByRole("radio"); const ids = radioButtons.map((button) => button.id); const uniqueIds = new Set(ids); // Should have unique IDs expect(uniqueIds.size).toBe(6); }); it("uses provided name for form association", () => { render(); const hiddenInputs = screen.getAllByDisplayValue("option1"); hiddenInputs.forEach((input) => { expect(input).toHaveAttribute("name", "test-group"); }); }); it("has proper form association", () => { render( ); const hiddenInputs = screen.getAllByDisplayValue("option1"); expect(hiddenInputs[0]).toHaveAttribute("name", "test-group"); expect(hiddenInputs[0]).toHaveAttribute("value", "option1"); expect(hiddenInputs[0]).not.toBeChecked(); const option2Inputs = screen.getAllByDisplayValue("option2"); expect(option2Inputs[0]).toHaveAttribute("name", "test-group"); expect(option2Inputs[0]).toHaveAttribute("value", "option2"); expect(option2Inputs[0]).toBeChecked(); }); it("maintains focus management", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); const { rerender } = render( ); const radioButtons = screen.getAllByRole("radio"); radioButtons[1].focus(); expect(radioButtons[1]).toHaveFocus(); // Change selection rerender( ); // Should still be focusable expect(radioButtons[1]).toHaveAttribute("tabIndex", "0"); }); it("supports screen reader navigation", () => { render(); const radioGroup = screen.getByRole("radiogroup"); const radioButtons = screen.getAllByRole("radio"); // RadioGroup should be present expect(radioGroup).toBeInTheDocument(); // All radio buttons should be in tab order radioButtons.forEach((button) => { expect(button).toHaveAttribute("tabIndex", "0"); expect(button).toHaveAttribute("role", "radio"); }); }); it("handles empty options gracefully", () => { render(); const radioGroup = screen.getByRole("radiogroup"); expect(radioGroup).toBeInTheDocument(); const radioButtons = screen.queryAllByRole("radio"); expect(radioButtons).toHaveLength(0); }); it("has proper accessible names", () => { render(); const radioButtons = screen.getAllByRole("radio"); radioButtons.forEach((button, index) => { const labelId = button.getAttribute("aria-labelledby"); const labelElement = document.getElementById(labelId); expect(labelElement).toHaveTextContent(`Option ${index + 1}`); }); }); it("maintains single selection behavior", async () => { const user = userEvent.setup(); const handleChange = vi.fn(); render( ); const radioButtons = screen.getAllByRole("radio"); // Click option 2 directly await user.click(radioButtons[1]); expect(handleChange).toHaveBeenCalledWith({ value: "option2" }); // Only one should be selected at a time expect(handleChange).toHaveBeenCalledTimes(2); }); });