|
@@ -0,0 +1,246 @@
|
|
|
|
|
+import React, { ReactNode } from "react"
|
|
|
|
|
+import { render, screen, fireEvent } from "@testing-library/react"
|
|
|
|
|
+import { SelectDropdown, DropdownOptionType } from "../select-dropdown"
|
|
|
|
|
+
|
|
|
|
|
+// Mock window.postMessage
|
|
|
|
|
+const postMessageMock = jest.fn()
|
|
|
|
|
+Object.defineProperty(window, "postMessage", {
|
|
|
|
|
+ writable: true,
|
|
|
|
|
+ value: postMessageMock,
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// Mock the Radix UI DropdownMenu component and its children
|
|
|
|
|
+jest.mock("../dropdown-menu", () => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ DropdownMenu: ({ children }: { children: ReactNode }) => <div data-testid="dropdown-root">{children}</div>,
|
|
|
|
|
+
|
|
|
|
|
+ DropdownMenuTrigger: ({
|
|
|
|
|
+ children,
|
|
|
|
|
+ disabled,
|
|
|
|
|
+ ...props
|
|
|
|
|
+ }: {
|
|
|
|
|
+ children: ReactNode
|
|
|
|
|
+ disabled?: boolean
|
|
|
|
|
+ [key: string]: any
|
|
|
|
|
+ }) => (
|
|
|
|
|
+ <button data-testid="dropdown-trigger" disabled={disabled} {...props}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ DropdownMenuContent: ({ children }: { children: ReactNode }) => (
|
|
|
|
|
+ <div data-testid="dropdown-content">{children}</div>
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ DropdownMenuItem: ({
|
|
|
|
|
+ children,
|
|
|
|
|
+ onClick,
|
|
|
|
|
+ disabled,
|
|
|
|
|
+ }: {
|
|
|
|
|
+ children: ReactNode
|
|
|
|
|
+ onClick?: () => void
|
|
|
|
|
+ disabled?: boolean
|
|
|
|
|
+ }) => (
|
|
|
|
|
+ <div data-testid="dropdown-item" onClick={onClick} aria-disabled={disabled}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ DropdownMenuSeparator: () => <div data-testid="dropdown-separator" />,
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+describe("SelectDropdown", () => {
|
|
|
|
|
+ const options = [
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ { value: "option2", label: "Option 2" },
|
|
|
|
|
+ { value: "option3", label: "Option 3" },
|
|
|
|
|
+ { value: "sep-1", label: "────", disabled: true },
|
|
|
|
|
+ { value: "action", label: "Action Item" },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ const onChangeMock = jest.fn()
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ jest.clearAllMocks()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("renders correctly with default props", () => {
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Check that the selected option is displayed in the trigger, not in a menu item
|
|
|
|
|
+ const trigger = screen.getByTestId("dropdown-trigger")
|
|
|
|
|
+ expect(trigger).toHaveTextContent("Option 1")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("handles disabled state correctly", () => {
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} disabled={true} />)
|
|
|
|
|
+
|
|
|
|
|
+ const trigger = screen.getByTestId("dropdown-trigger")
|
|
|
|
|
+ expect(trigger).toHaveAttribute("disabled")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("renders with width: 100% for proper sizing", () => {
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ const trigger = screen.getByTestId("dropdown-trigger")
|
|
|
|
|
+ expect(trigger).toHaveStyle("width: 100%")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("passes the selected value to the trigger", () => {
|
|
|
|
|
+ const { rerender } = render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Check initial render using testId to be specific
|
|
|
|
|
+ const trigger = screen.getByTestId("dropdown-trigger")
|
|
|
|
|
+ expect(trigger).toHaveTextContent("Option 1")
|
|
|
|
|
+
|
|
|
|
|
+ // Rerender with a different value
|
|
|
|
|
+ rerender(<SelectDropdown value="option3" options={options} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Check updated render
|
|
|
|
|
+ expect(trigger).toHaveTextContent("Option 3")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("applies custom className to trigger when provided", () => {
|
|
|
|
|
+ render(
|
|
|
|
|
+ <SelectDropdown
|
|
|
|
|
+ value="option1"
|
|
|
|
|
+ options={options}
|
|
|
|
|
+ onChange={onChangeMock}
|
|
|
|
|
+ triggerClassName="custom-trigger-class"
|
|
|
|
|
+ />,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ const trigger = screen.getByTestId("dropdown-trigger")
|
|
|
|
|
+ expect(trigger.classList.toString()).toContain("custom-trigger-class")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Tests for the new functionality
|
|
|
|
|
+ describe("Option types", () => {
|
|
|
|
|
+ it("renders separator options correctly", () => {
|
|
|
|
|
+ const optionsWithTypedSeparator = [
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ { value: "sep-1", label: "Separator", type: DropdownOptionType.SEPARATOR },
|
|
|
|
|
+ { value: "option2", label: "Option 2" },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={optionsWithTypedSeparator} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Check for separator
|
|
|
|
|
+ const separators = screen.getAllByTestId("dropdown-separator")
|
|
|
|
|
+ expect(separators.length).toBe(1)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("renders string separator (backward compatibility) correctly", () => {
|
|
|
|
|
+ const optionsWithStringSeparator = [
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ { value: "sep-1", label: "────", disabled: true },
|
|
|
|
|
+ { value: "option2", label: "Option 2" },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={optionsWithStringSeparator} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Check for separator
|
|
|
|
|
+ const separators = screen.getAllByTestId("dropdown-separator")
|
|
|
|
|
+ expect(separators.length).toBe(1)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("renders shortcut options correctly", () => {
|
|
|
|
|
+ const shortcutText = "Ctrl+K"
|
|
|
|
|
+ const optionsWithShortcut = [
|
|
|
|
|
+ { value: "shortcut", label: shortcutText, type: DropdownOptionType.SHORTCUT },
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ render(
|
|
|
|
|
+ <SelectDropdown
|
|
|
|
|
+ value="option1"
|
|
|
|
|
+ options={optionsWithShortcut}
|
|
|
|
|
+ onChange={onChangeMock}
|
|
|
|
|
+ shortcutText={shortcutText}
|
|
|
|
|
+ />,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // The shortcut text should be rendered as a div, not a dropdown item
|
|
|
|
|
+ expect(screen.queryByText(shortcutText)).toBeInTheDocument()
|
|
|
|
|
+ const dropdownItems = screen.getAllByTestId("dropdown-item")
|
|
|
|
|
+ expect(dropdownItems.length).toBe(1) // Only one regular option
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("handles action options correctly", () => {
|
|
|
|
|
+ const optionsWithAction = [
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ { value: "settingsButtonClicked", label: "Settings", type: DropdownOptionType.ACTION },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={optionsWithAction} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Get all dropdown items
|
|
|
|
|
+ const dropdownItems = screen.getAllByTestId("dropdown-item")
|
|
|
|
|
+
|
|
|
|
|
+ // Click the action item
|
|
|
|
|
+ fireEvent.click(dropdownItems[1])
|
|
|
|
|
+
|
|
|
|
|
+ // Check that postMessage was called with the correct action
|
|
|
|
|
+ expect(postMessageMock).toHaveBeenCalledWith({
|
|
|
|
|
+ type: "action",
|
|
|
|
|
+ action: "settingsButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // The onChange callback should not be called for action items
|
|
|
|
|
+ expect(onChangeMock).not.toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("only treats options with explicit ACTION type as actions", () => {
|
|
|
|
|
+ const optionsForTest = [
|
|
|
|
|
+ { value: "option1", label: "Option 1" },
|
|
|
|
|
+ // This should be treated as a regular option despite the -action suffix
|
|
|
|
|
+ { value: "settings-action", label: "Regular option with action suffix" },
|
|
|
|
|
+ // This should be treated as an action
|
|
|
|
|
+ { value: "settingsButtonClicked", label: "Settings", type: DropdownOptionType.ACTION },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={optionsForTest} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Get all dropdown items
|
|
|
|
|
+ const dropdownItems = screen.getAllByTestId("dropdown-item")
|
|
|
|
|
+
|
|
|
|
|
+ // Click the second option (with action suffix but no ACTION type)
|
|
|
|
|
+ fireEvent.click(dropdownItems[1])
|
|
|
|
|
+
|
|
|
|
|
+ // Should trigger onChange, not postMessage
|
|
|
|
|
+ expect(onChangeMock).toHaveBeenCalledWith("settings-action")
|
|
|
|
|
+ expect(postMessageMock).not.toHaveBeenCalled()
|
|
|
|
|
+
|
|
|
|
|
+ // Reset mocks
|
|
|
|
|
+ onChangeMock.mockReset()
|
|
|
|
|
+ postMessageMock.mockReset()
|
|
|
|
|
+
|
|
|
|
|
+ // Click the third option (ACTION type)
|
|
|
|
|
+ fireEvent.click(dropdownItems[2])
|
|
|
|
|
+
|
|
|
|
|
+ // Should trigger postMessage with "settingsButtonClicked", not onChange
|
|
|
|
|
+ expect(postMessageMock).toHaveBeenCalledWith({
|
|
|
|
|
+ type: "action",
|
|
|
|
|
+ action: "settingsButtonClicked",
|
|
|
|
|
+ })
|
|
|
|
|
+ expect(onChangeMock).not.toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("calls onChange for regular menu items", () => {
|
|
|
|
|
+ render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)
|
|
|
|
|
+
|
|
|
|
|
+ // Get all dropdown items
|
|
|
|
|
+ const dropdownItems = screen.getAllByTestId("dropdown-item")
|
|
|
|
|
+
|
|
|
|
|
+ // Click the second option (index 1)
|
|
|
|
|
+ fireEvent.click(dropdownItems[1])
|
|
|
|
|
+
|
|
|
|
|
+ // Check that onChange was called with the correct value
|
|
|
|
|
+ expect(onChangeMock).toHaveBeenCalledWith("option2")
|
|
|
|
|
+
|
|
|
|
|
+ // postMessage should not be called for regular items
|
|
|
|
|
+ expect(postMessageMock).not.toHaveBeenCalled()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+})
|