148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
import { useState } from "react";
|
|
|
|
import { render, screen, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { Field, FieldDescription, FieldError } from "./field";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectLabel,
|
|
SelectSeparator,
|
|
SelectTrigger,
|
|
SelectValue
|
|
} from "./select";
|
|
|
|
function ReviewLaneSelect(props?: React.ComponentProps<typeof Select>) {
|
|
return (
|
|
<Select {...props}>
|
|
<SelectTrigger aria-label="Review lane">
|
|
<SelectValue placeholder="Choose a review lane" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectLabel>Review lane</SelectLabel>
|
|
<SelectItem value="editorial">Editorial review</SelectItem>
|
|
<SelectItem value="design">Design review</SelectItem>
|
|
<SelectSeparator />
|
|
<SelectItem value="legal">Legal review</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
}
|
|
|
|
describe("Select", () => {
|
|
it("renders default value and opens selectable content", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(<ReviewLaneSelect defaultValue="design" />);
|
|
|
|
const trigger = screen.getByRole("combobox", { name: "Review lane" });
|
|
|
|
expect(trigger).toHaveTextContent("Design review");
|
|
expect(trigger).toHaveAttribute("data-slot", "trigger");
|
|
|
|
await user.click(trigger);
|
|
|
|
const listbox = await screen.findByRole("listbox");
|
|
const designOption = within(listbox).getByRole("option", { name: "Design review" });
|
|
|
|
expect(listbox).toHaveAttribute("data-slot", "content");
|
|
expect(designOption).toHaveAttribute("data-slot", "item");
|
|
});
|
|
|
|
it("updates controlled value after selecting an option", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
function ControlledSelect() {
|
|
const [value, setValue] = useState("editorial");
|
|
|
|
return <ReviewLaneSelect value={value} onValueChange={setValue} />;
|
|
}
|
|
|
|
render(<ControlledSelect />);
|
|
|
|
const trigger = screen.getByRole("combobox", { name: "Review lane" });
|
|
expect(trigger).toHaveTextContent("Editorial review");
|
|
|
|
await user.click(trigger);
|
|
await user.click(await screen.findByRole("option", { name: "Legal review" }));
|
|
|
|
expect(trigger).toHaveTextContent("Legal review");
|
|
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("supports field invalid state and described-by wiring", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<Field id="routing" invalid>
|
|
<Select>
|
|
<SelectTrigger aria-label="Routing team">
|
|
<SelectValue placeholder="Choose a team" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="product">Product</SelectItem>
|
|
<SelectItem value="design">Design</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FieldDescription>Choose the primary owner.</FieldDescription>
|
|
<FieldError>Select a team before publishing.</FieldError>
|
|
</Field>
|
|
);
|
|
|
|
const trigger = screen.getByRole("combobox", { name: "Routing team" });
|
|
|
|
expect(trigger).toHaveAttribute("aria-invalid", "true");
|
|
expect(trigger).toHaveAttribute(
|
|
"aria-describedby",
|
|
expect.stringContaining("routing-description")
|
|
);
|
|
expect(trigger).toHaveAttribute(
|
|
"aria-describedby",
|
|
expect.stringContaining("routing-error")
|
|
);
|
|
|
|
await user.click(trigger);
|
|
expect(await screen.findByRole("option", { name: "Product" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("exposes disabled state on the trigger", () => {
|
|
render(
|
|
<Select disabled>
|
|
<SelectTrigger aria-label="Disabled select">
|
|
<SelectValue placeholder="Disabled" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="one">One</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
);
|
|
|
|
const trigger = screen.getByRole("combobox", { name: "Disabled select" });
|
|
|
|
expect(trigger).toBeDisabled();
|
|
expect(trigger).toHaveAttribute("data-disabled", "");
|
|
});
|
|
|
|
it("opens from the keyboard so combobox interaction stays accessible", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(<ReviewLaneSelect defaultValue="editorial" />);
|
|
|
|
const trigger = screen.getByRole("combobox", { name: "Review lane" });
|
|
|
|
trigger.focus();
|
|
await user.keyboard("{ArrowDown}");
|
|
|
|
const listbox = await screen.findByRole("listbox");
|
|
|
|
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
expect(within(listbox).getByRole("option", { name: "Editorial review" })).toBeInTheDocument();
|
|
});
|
|
});
|