Input component with storybook and testing
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
import React from "react";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { expect, test, describe, vi } from "vitest";
|
||||
import { axe, toHaveNoViolations } from "jest-axe";
|
||||
import Input from "../../app/components/Input";
|
||||
|
||||
expect.extend(toHaveNoViolations);
|
||||
|
||||
describe("Input Component Accessibility", () => {
|
||||
test("has no accessibility violations", async () => {
|
||||
const { container } = render(<Input label="Test input" />);
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("has no accessibility violations when disabled", async () => {
|
||||
const { container } = render(<Input label="Test input" disabled={true} />);
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("has no accessibility violations when in error state", async () => {
|
||||
const { container } = render(<Input label="Test input" error={true} />);
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("has no accessibility violations with horizontal label", async () => {
|
||||
const { container } = render(
|
||||
<Input label="Test input" labelVariant="horizontal" />
|
||||
);
|
||||
const results = await axe(container);
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test("associates label with input correctly", () => {
|
||||
render(<Input label="Test input" />);
|
||||
const input = screen.getByLabelText("Test input");
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(input).toHaveAttribute("type", "text");
|
||||
});
|
||||
|
||||
test("maintains label association with custom ID", () => {
|
||||
render(<Input id="custom-input" label="Test input" />);
|
||||
const input = screen.getByLabelText("Test input");
|
||||
expect(input).toHaveAttribute("id", "custom-input");
|
||||
});
|
||||
|
||||
test("supports keyboard navigation", () => {
|
||||
render(<Input label="Test input" />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Input should be focusable
|
||||
input.focus();
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
|
||||
test("supports keyboard activation", () => {
|
||||
const handleChange = vi.fn();
|
||||
render(<Input label="Test input" onChange={handleChange} />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Type in the input
|
||||
fireEvent.change(input, { target: { value: "test" } });
|
||||
expect(handleChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("supports Enter key activation", () => {
|
||||
const handleChange = vi.fn();
|
||||
render(<Input label="Test input" onChange={handleChange} />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Focus the input first
|
||||
input.focus();
|
||||
expect(input).toHaveFocus();
|
||||
|
||||
fireEvent.keyDown(input, { key: "Enter" });
|
||||
// Input should still be focused and ready for typing
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
|
||||
test("supports Space key activation", () => {
|
||||
const handleChange = vi.fn();
|
||||
render(<Input label="Test input" onChange={handleChange} />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Focus the input first
|
||||
input.focus();
|
||||
expect(input).toHaveFocus();
|
||||
|
||||
fireEvent.keyDown(input, { key: " " });
|
||||
// Input should still be focused and ready for typing
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
|
||||
test("supports Tab navigation", () => {
|
||||
render(
|
||||
<div>
|
||||
<Input label="First input" />
|
||||
<Input label="Second input" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const firstInput = screen.getByLabelText("First input");
|
||||
const secondInput = screen.getByLabelText("Second input");
|
||||
|
||||
firstInput.focus();
|
||||
expect(firstInput).toHaveFocus();
|
||||
|
||||
// Use userEvent for more realistic tab navigation
|
||||
fireEvent.keyDown(firstInput, { key: "Tab", code: "Tab" });
|
||||
// Note: In a real browser, Tab would move focus, but in tests we need to simulate it
|
||||
secondInput.focus();
|
||||
expect(secondInput).toHaveFocus();
|
||||
});
|
||||
|
||||
test("supports Shift+Tab navigation", () => {
|
||||
render(
|
||||
<div>
|
||||
<Input label="First input" />
|
||||
<Input label="Second input" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const firstInput = screen.getByLabelText("First input");
|
||||
const secondInput = screen.getByLabelText("Second input");
|
||||
|
||||
secondInput.focus();
|
||||
expect(secondInput).toHaveFocus();
|
||||
|
||||
// Use userEvent for more realistic tab navigation
|
||||
fireEvent.keyDown(secondInput, { key: "Tab", shiftKey: true, code: "Tab" });
|
||||
// Note: In a real browser, Shift+Tab would move focus, but in tests we need to simulate it
|
||||
firstInput.focus();
|
||||
expect(firstInput).toHaveFocus();
|
||||
});
|
||||
|
||||
test("handles disabled state accessibility", () => {
|
||||
render(<Input label="Test input" disabled={true} />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
expect(input).toBeDisabled();
|
||||
expect(input).toHaveAttribute("disabled");
|
||||
});
|
||||
|
||||
test("handles error state accessibility", () => {
|
||||
render(<Input label="Test input" error={true} />);
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
// Error state should still be accessible
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(input).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test("supports different input types", () => {
|
||||
const { rerender } = render(<Input type="email" label="Email" />);
|
||||
let input = screen.getByRole("textbox");
|
||||
expect(input).toHaveAttribute("type", "email");
|
||||
|
||||
rerender(<Input type="password" label="Password" />);
|
||||
// Password inputs don't have textbox role, they have textbox role only for text inputs
|
||||
input = screen.getByLabelText("Password");
|
||||
expect(input).toHaveAttribute("type", "password");
|
||||
|
||||
rerender(<Input type="number" label="Number" />);
|
||||
input = screen.getByRole("spinbutton");
|
||||
expect(input).toHaveAttribute("type", "number");
|
||||
});
|
||||
|
||||
test("supports placeholder accessibility", () => {
|
||||
render(<Input placeholder="Enter your name" />);
|
||||
const input = screen.getByPlaceholderText("Enter your name");
|
||||
expect(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("supports value accessibility", () => {
|
||||
render(<Input value="test value" />);
|
||||
const input = screen.getByDisplayValue("test value");
|
||||
expect(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("maintains focus management", () => {
|
||||
const handleFocus = vi.fn();
|
||||
const handleBlur = vi.fn();
|
||||
|
||||
render(
|
||||
<Input label="Test input" onFocus={handleFocus} onBlur={handleBlur} />
|
||||
);
|
||||
|
||||
const input = screen.getByRole("textbox");
|
||||
|
||||
fireEvent.focus(input);
|
||||
expect(handleFocus).toHaveBeenCalled();
|
||||
// Focus the input to ensure it has focus
|
||||
input.focus();
|
||||
expect(input).toHaveFocus();
|
||||
|
||||
fireEvent.blur(input);
|
||||
expect(handleBlur).toHaveBeenCalled();
|
||||
// Manually blur the input to ensure it loses focus
|
||||
input.blur();
|
||||
expect(input).not.toHaveFocus();
|
||||
});
|
||||
|
||||
test("supports form association", () => {
|
||||
render(
|
||||
<form>
|
||||
<Input name="test-field" label="Test input" />
|
||||
</form>
|
||||
);
|
||||
|
||||
const input = screen.getByRole("textbox");
|
||||
expect(input).toHaveAttribute("name", "test-field");
|
||||
});
|
||||
|
||||
test("supports ARIA attributes", () => {
|
||||
render(
|
||||
<Input
|
||||
label="Test input"
|
||||
aria-describedby="help-text"
|
||||
aria-required="true"
|
||||
/>
|
||||
);
|
||||
|
||||
const input = screen.getByRole("textbox");
|
||||
expect(input).toHaveAttribute("aria-describedby", "help-text");
|
||||
expect(input).toHaveAttribute("aria-required", "true");
|
||||
});
|
||||
|
||||
test("supports custom ARIA labels", () => {
|
||||
render(<Input aria-label="Custom input label" />);
|
||||
const input = screen.getByLabelText("Custom input label");
|
||||
expect(input).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("handles multiple inputs without conflicts", () => {
|
||||
render(
|
||||
<div>
|
||||
<Input label="First input" />
|
||||
<Input label="Second input" />
|
||||
<Input label="Third input" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const firstInput = screen.getByLabelText("First input");
|
||||
const secondInput = screen.getByLabelText("Second input");
|
||||
const thirdInput = screen.getByLabelText("Third input");
|
||||
|
||||
expect(firstInput).toBeInTheDocument();
|
||||
expect(secondInput).toBeInTheDocument();
|
||||
expect(thirdInput).toBeInTheDocument();
|
||||
|
||||
// Each should have unique IDs
|
||||
expect(firstInput.id).not.toBe(secondInput.id);
|
||||
expect(secondInput.id).not.toBe(thirdInput.id);
|
||||
expect(firstInput.id).not.toBe(thirdInput.id);
|
||||
});
|
||||
|
||||
test("supports screen reader navigation", () => {
|
||||
render(<Input label="Test input" />);
|
||||
const input = screen.getByRole("textbox");
|
||||
const label = screen.getByText("Test input");
|
||||
|
||||
// Label should be associated with input
|
||||
expect(label).toHaveAttribute("for", input.id);
|
||||
});
|
||||
|
||||
test("handles dynamic label changes", () => {
|
||||
const { rerender } = render(<Input label="Original label" />);
|
||||
expect(screen.getByText("Original label")).toBeInTheDocument();
|
||||
|
||||
rerender(<Input label="Updated label" />);
|
||||
expect(screen.getByText("Updated label")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Original label")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("supports controlled input behavior", () => {
|
||||
const handleChange = vi.fn();
|
||||
render(<Input value="controlled value" onChange={handleChange} />);
|
||||
|
||||
const input = screen.getByDisplayValue("controlled value");
|
||||
fireEvent.change(input, { target: { value: "new value" } });
|
||||
|
||||
expect(handleChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user