Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions webview-ui/src/components/ui/__tests__/select-dropdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ describe("SelectDropdown", () => {
expect(trigger.classList.toString()).toContain("custom-trigger-class")
})

it("ensures open state is controlled via props", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test "ensures open state is controlled via props" only checks the presence of elements without simulating any interactions that change the open state. Consider adding tests to simulate opening the dropdown and then triggering the escape key and outside clicks to ensure the onEscapeKeyDown and onInteractOutside handlers properly close the dropdown.

// Test that the component accepts and uses the open state controlled prop
render(<SelectDropdown value="option1" options={options} onChange={onChangeMock} />)

// The component should render the dropdown root with correct props
const dropdown = screen.getByTestId("dropdown-root")
expect(dropdown).toBeInTheDocument()

// Verify trigger and content are rendered
const trigger = screen.getByTestId("dropdown-trigger")
const content = screen.getByTestId("dropdown-content")
expect(trigger).toBeInTheDocument()
expect(content).toBeInTheDocument()
})

// Tests for the new functionality
describe("Option types", () => {
it("renders separator options correctly", () => {
Expand All @@ -131,20 +146,6 @@ describe("SelectDropdown", () => {
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 = [
Expand Down
23 changes: 21 additions & 2 deletions webview-ui/src/components/ui/select-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DropdownMenuSeparator,
} from "./dropdown-menu"
import { cn } from "@/lib/utils"
import { useEffect, useState } from "react"

// Constants for option types
export enum DropdownOptionType {
Expand Down Expand Up @@ -57,6 +58,19 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
},
ref,
) => {
// Track open state
const [open, setOpen] = React.useState(false)
const [portalContainer, setPortalContainer] = useState<HTMLElement>()

useEffect(() => {
// The dropdown menu uses a portal from @shadcn/ui which by default renders
// at the document root. This causes the menu to remain visible even when
// the parent ChatView component is hidden (during settings/history view).
// By moving the portal inside ChatView, the menu will properly hide when
// its parent is hidden.
setPortalContainer(document.getElementById("chat-view-portal") || undefined)
}, [])

// Find the selected option label
const selectedOption = options.find((option) => option.value === value)
const displayText = selectedOption?.label || placeholder || ""
Expand All @@ -69,13 +83,15 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
type: "action",
action: option.value,
})
setOpen(false)
return
}
onChange(option.value)
setOpen(false)
}

return (
<DropdownMenu>
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
ref={ref}
disabled={disabled}
Expand Down Expand Up @@ -112,13 +128,16 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
<DropdownMenuContent
align={align}
sideOffset={sideOffset}
onEscapeKeyDown={() => setOpen(false)}
onInteractOutside={() => setOpen(false)}
container={portalContainer}
className={cn(
"bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50",
contentClassName,
)}>
{options.map((option, index) => {
// Handle separator type
if (option.type === DropdownOptionType.SEPARATOR || option.label.includes("────")) {
if (option.type === DropdownOptionType.SEPARATOR) {
return <DropdownMenuSeparator key={`sep-${index}`} />
}

Expand Down