diff --git a/ui/components/shadcn/combobox/combobox.test.tsx b/ui/components/shadcn/combobox/combobox.test.tsx new file mode 100644 index 0000000000..f5ea9e492d --- /dev/null +++ b/ui/components/shadcn/combobox/combobox.test.tsx @@ -0,0 +1,94 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeAll, describe, expect, it, vi } from "vitest"; + +import { Combobox } from "./combobox"; + +const options = [ + { value: "aws", label: "AWS" }, + { value: "azure", label: "Azure" }, + { value: "gcp", label: "GCP" }, +]; + +beforeAll(() => { + global.ResizeObserver = class ResizeObserver { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + } as unknown as typeof ResizeObserver; + + Object.defineProperty(HTMLElement.prototype, "scrollIntoView", { + configurable: true, + value: vi.fn(), + }); + + Object.defineProperty(HTMLElement.prototype, "hasPointerCapture", { + configurable: true, + value: vi.fn(() => false), + }); + Object.defineProperty(HTMLElement.prototype, "releasePointerCapture", { + configurable: true, + value: vi.fn(), + }); +}); + +describe("Combobox", () => { + it("renders a selectable combobox trigger", () => { + // Given + render(); + + // When + const trigger = screen.getByRole("combobox", { name: /select provider/i }); + + // Then + expect(trigger).toBeVisible(); + expect(trigger).toHaveAttribute("aria-expanded", "false"); + }); + + it("uses visible trigger and chevron open-state motion", () => { + // Given + render(); + + // When + const trigger = screen.getByRole("combobox", { name: /select provider/i }); + const icon = trigger.querySelector("svg"); + + // Then + expect(trigger).toHaveClass( + "group", + "transition-[background-color,border-color,color,box-shadow]", + ); + expect(icon).toHaveClass( + "transition-transform", + "duration-200", + "ease-out", + "group-aria-expanded:rotate-180", + "motion-reduce:rotate-0", + "motion-reduce:transition-none", + ); + }); + + it("opens with the shared Popover content motion contract", async () => { + // Given + const user = userEvent.setup(); + render(); + + // When + await user.click( + screen.getByRole("combobox", { name: /select provider/i }), + ); + const content = document.querySelector("[data-slot='popover-content']"); + + // Then + expect(content).toHaveClass( + "duration-200", + "ease-out", + "data-[state=open]:animate-in", + "data-[state=open]:fade-in-0", + "data-[state=open]:zoom-in-95", + "motion-reduce:animate-none", + "motion-reduce:transform-none", + "motion-reduce:transition-none", + ); + }); +}); diff --git a/ui/components/shadcn/combobox/combobox.tsx b/ui/components/shadcn/combobox/combobox.tsx index b10bcf8973..8279a4147b 100644 --- a/ui/components/shadcn/combobox/combobox.tsx +++ b/ui/components/shadcn/combobox/combobox.tsx @@ -113,8 +113,10 @@ export function Combobox({ variant="outline" role="combobox" aria-expanded={open} + aria-label={selectedOption ? selectedOption.label : placeholder} disabled={disabled} className={cn( + "group transition-[background-color,border-color,color,box-shadow]", comboboxTriggerVariants({ variant }), triggerClassName, className, @@ -123,7 +125,7 @@ export function Combobox({ {selectedOption ? selectedOption.label : placeholder} - +