Files
web/packages/design-system/lib/components/Input/Input.test.tsx
Anton Gunnarsson e740c1ba66 Merged in fix/remove-redundant-test (pull request #3457)
fix: Remove required test after removing it from input

* Remove required test after removing it from input


Approved-by: Bianca Widstam
Approved-by: Emma Zettervall
2026-01-20 12:53:12 +00:00

195 lines
6.0 KiB
TypeScript

import { describe, expect, it, vi, afterEach } from "vitest"
import { render, screen, fireEvent, cleanup } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Input } from "./Input"
import { TextField } from "react-aria-components"
afterEach(() => {
cleanup()
})
// Wrap Input in TextField for proper React Aria context
const renderInput = (props: React.ComponentProps<typeof Input>) => {
return render(
<TextField>
<Input {...props} />
</TextField>
)
}
// Render Input standalone (without TextField) for testing Input's own behavior
const renderInputStandalone = (props: React.ComponentProps<typeof Input>) => {
return render(<Input {...props} />)
}
describe("Input", () => {
describe("props", () => {
it("applies readOnly attribute", () => {
renderInput({ label: "Email", readOnly: true })
expect(screen.getByRole("textbox")).toHaveProperty("readOnly", true)
})
it("applies placeholder for floating label when provided", () => {
renderInput({ label: "Email", placeholder: "Enter email" })
expect(screen.getByRole("textbox").getAttribute("placeholder")).toBe(
"Enter email"
)
})
it("applies empty placeholder for top label by default", () => {
renderInput({ label: "Email", labelPosition: "top" })
expect(screen.getByRole("textbox").getAttribute("placeholder")).toBe("")
})
it("applies custom id", () => {
// Use standalone render since TextField overrides id via context
renderInputStandalone({ label: "Email", id: "custom-id" })
expect(screen.getByRole("textbox").getAttribute("id")).toBe("custom-id")
})
it("applies aria-describedby", () => {
renderInput({ label: "Email", "aria-describedby": "error-message" })
expect(screen.getByRole("textbox").getAttribute("aria-describedby")).toBe(
"error-message"
)
})
})
describe("clear content button", () => {
it("does not show clear button when showClearContentIcon is false", () => {
renderInput({
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: false,
})
expect(screen.queryByLabelText("Clear content")).toBeNull()
})
it("does not show clear button when input is empty", async () => {
renderInput({
label: "Email",
value: "",
onChange: vi.fn(),
showClearContentIcon: true,
})
expect(await screen.findByRole("textbox")).toBeTruthy()
expect(screen.queryByLabelText("Clear content")).toBeNull()
})
it("shows clear button when input has value and showClearContentIcon is true", async () => {
renderInput({
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: true,
})
expect(await screen.findByLabelText("Clear content")).toBeTruthy()
})
})
describe("icons", () => {
it("renders left icon when provided", async () => {
renderInput({
label: "Search",
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
leftIcon: <span data-testid="left-icon">🔍</span>,
})
expect(await screen.findByTestId("left-icon")).toBeTruthy()
})
it("renders right icon when provided", () => {
renderInput({
label: "Password",
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(screen.getByTestId("right-icon")).toBeTruthy()
})
it("hides right icon when clear button is shown", async () => {
renderInput({
label: "Email",
value: "test",
onChange: vi.fn(),
showClearContentIcon: true,
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(await screen.findByLabelText("Clear content")).toBeTruthy()
expect(screen.queryByTestId("right-icon")).toBeNull()
})
it("shows right icon when clear button condition not met", () => {
renderInput({
label: "Email",
value: "",
onChange: vi.fn(),
showClearContentIcon: true,
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
rightIcon: <span data-testid="right-icon">👁</span>,
})
expect(screen.getByTestId("right-icon")).toBeTruthy()
})
})
describe("controlled input", () => {
it("displays the controlled value", () => {
renderInput({
label: "Email",
value: "test@example.com",
onChange: vi.fn(),
})
expect(screen.getByRole("textbox")).toHaveProperty(
"value",
"test@example.com"
)
})
it("calls onChange when typing", async () => {
const onChange = vi.fn()
renderInput({ label: "Email", value: "", onChange })
const input = screen.getByRole("textbox")
await userEvent.type(input, "a")
expect(onChange).toHaveBeenCalled()
})
it("does not change value without onChange updating it", () => {
const onChange = vi.fn()
renderInput({ label: "Email", value: "initial", onChange })
const input = screen.getByRole("textbox")
fireEvent.change(input, { target: { value: "changed" } })
// Value stays the same because it's controlled
expect(input).toHaveProperty("value", "initial")
})
})
describe("ref forwarding", () => {
it("forwards ref to the input element", () => {
const ref = { current: null as HTMLInputElement | null }
render(
<TextField>
<Input label="Email" ref={ref} />
</TextField>
)
expect(ref.current).toBeInstanceOf(HTMLInputElement)
})
it("allows focusing via ref", () => {
const ref = { current: null as HTMLInputElement | null }
render(
<TextField>
<Input label="Email" ref={ref} />
</TextField>
)
ref.current?.focus()
expect(document.activeElement).toBe(ref.current)
})
})
})