mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-07 02:58:15 +00:00
UI improvements
This commit is contained in:
parent
461494a3cc
commit
89a94b885a
4 changed files with 363 additions and 25 deletions
|
|
@ -9,9 +9,10 @@ export interface SettingsSearchInputProps {
|
|||
onChange: (value: string) => void
|
||||
onFocus?: () => void
|
||||
onBlur?: () => void
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
export function SettingsSearchInput({ value, onChange, onFocus, onBlur }: SettingsSearchInputProps) {
|
||||
export function SettingsSearchInput({ value, onChange, onFocus, onBlur, onKeyDown }: SettingsSearchInputProps) {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
return (
|
||||
|
|
@ -24,10 +25,11 @@ export function SettingsSearchInput({ value, onChange, onFocus, onBlur }: Settin
|
|||
onChange={(e) => onChange(e.target.value)}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={t("settings:search.placeholder")}
|
||||
className={cn(
|
||||
"pl-6 w-[0px] focus:pl-8 focus:min-w-[130px] focus:w-full active:w-full",
|
||||
value && "pr-8",
|
||||
"pl-6 w-[0px] border-none focus:border-vscode-input-border focus:pl-8 focus:min-w-[130px] focus:w-full",
|
||||
value && "pr-4 min-w-[150px]",
|
||||
)}
|
||||
/>
|
||||
{value && (
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import type { LucideIcon } from "lucide-react"
|
|||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import type { SearchResult } from "@/hooks/useSettingsSearch"
|
||||
import type { SectionName } from "@/utils/parseSettingsI18nKeys"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface SettingsSearchResultsProps {
|
||||
results: SearchResult[]
|
||||
query: string
|
||||
onSelectResult: (result: SearchResult) => void
|
||||
sections: { id: SectionName; icon: LucideIcon }[]
|
||||
highlightedResultId?: string
|
||||
}
|
||||
|
||||
interface HighlightMatchProps {
|
||||
|
|
@ -44,7 +46,13 @@ function HighlightMatch({ text, query }: HighlightMatchProps) {
|
|||
)
|
||||
}
|
||||
|
||||
export function SettingsSearchResults({ results, query, onSelectResult, sections }: SettingsSearchResultsProps) {
|
||||
export function SettingsSearchResults({
|
||||
results,
|
||||
query,
|
||||
onSelectResult,
|
||||
sections,
|
||||
highlightedResultId,
|
||||
}: SettingsSearchResultsProps) {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
// Group results by tab
|
||||
|
|
@ -70,42 +78,55 @@ export function SettingsSearchResults({ results, query, onSelectResult, sections
|
|||
// If no results, show a message
|
||||
if (results.length === 0) {
|
||||
return (
|
||||
<div className="max-h-80 overflow-y-auto p-4 text-vscode-descriptionForeground text-sm">
|
||||
<div className="max-h-80 overflow-y-auto p-4 bg-vscode-sideBar-background text-vscode-descriptionForeground text-sm">
|
||||
{t("settings:search.noResults", { query })}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-h-80 overflow-y-auto">
|
||||
<div className="max-h-80 overflow-y-auto bg-vscode-sideBar-background" role="listbox">
|
||||
{Object.entries(groupedResults).map(([tab, tabResults]) => {
|
||||
const Icon = sectionIconMap.get(tab as SectionName)
|
||||
|
||||
return (
|
||||
<div key={tab}>
|
||||
{/* Tab header */}
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 text-xs text-vscode-descriptionForeground bg-vscode-sideBar-background sticky top-0">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 mt-4 text-xs text-vscode-descriptionForeground bg-vscode-sideBar-background border-b-vscode-panel-border sticky top-0">
|
||||
{Icon && <Icon className="h-3.5 w-3.5" />}
|
||||
<span>{t(`settings:sections.${tab}`)}</span>
|
||||
</div>
|
||||
|
||||
{/* Result items */}
|
||||
{tabResults.map((result) => (
|
||||
<button
|
||||
key={result.id}
|
||||
type="button"
|
||||
onClick={() => onSelectResult(result)}
|
||||
className="w-full text-left px-3 py-2 hover:bg-vscode-list-hoverBackground focus:bg-vscode-list-hoverBackground focus:outline-none">
|
||||
<div className="text-sm text-vscode-foreground">
|
||||
<HighlightMatch text={result.translatedLabel} query={query} />
|
||||
</div>
|
||||
{result.translatedDescription && (
|
||||
<div className="text-xs text-vscode-descriptionForeground truncate mt-0.5">
|
||||
<HighlightMatch text={result.translatedDescription} query={query} />
|
||||
{tabResults.map((result) => {
|
||||
const isHighlighted = highlightedResultId === result.id
|
||||
const resultDomId = `settings-search-result-${result.id}`
|
||||
|
||||
return (
|
||||
<button
|
||||
key={result.id}
|
||||
id={resultDomId}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={isHighlighted}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => onSelectResult(result)}
|
||||
className={cn(
|
||||
"w-full cursor-pointer text-left px-3 py-2 hover:bg-vscode-list-hoverBackground focus:bg-vscode-list-hoverBackground focus:outline-none",
|
||||
isHighlighted &&
|
||||
"bg-vscode-list-activeSelectionBackground text-vscode-foreground",
|
||||
)}>
|
||||
<div className="text-sm text-vscode-foreground">
|
||||
<HighlightMatch text={result.translatedLabel} query={query} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{result.translatedDescription && (
|
||||
<div className="text-xs text-vscode-descriptionForeground truncate mt-0.5">
|
||||
<HighlightMatch text={result.translatedDescription} query={query} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [isSearchFocused, setIsSearchFocused] = useState(false)
|
||||
const [highlightedResultId, setHighlightedResultId] = useState<string | undefined>(undefined)
|
||||
|
||||
const scrollPositions = useRef<Record<SectionName, number>>(
|
||||
Object.fromEntries(sectionNames.map((s) => [s, 0])) as Record<SectionName, number>,
|
||||
|
|
@ -590,6 +591,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
(result: SearchResult) => {
|
||||
setSearchQuery("")
|
||||
setIsSearchFocused(false)
|
||||
setHighlightedResultId(undefined)
|
||||
handleTabChange(result.tab)
|
||||
// Small delay to allow tab switch and render
|
||||
setTimeout(() => scrollToSetting(result.id), 150)
|
||||
|
|
@ -597,10 +599,76 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
[handleTabChange, scrollToSetting],
|
||||
)
|
||||
|
||||
// Keyboard navigation inside search results
|
||||
const moveHighlight = useCallback(
|
||||
(direction: 1 | -1) => {
|
||||
if (!searchResults.length) return
|
||||
const flatIds = searchResults.map((r) => r.id)
|
||||
const currentIndex = highlightedResultId ? flatIds.indexOf(highlightedResultId) : -1
|
||||
const nextIndex = (currentIndex + direction + flatIds.length) % flatIds.length
|
||||
setHighlightedResultId(flatIds[nextIndex])
|
||||
},
|
||||
[highlightedResultId, searchResults],
|
||||
)
|
||||
|
||||
const handleSearchKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!searchResults.length) return
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
moveHighlight(1)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp") {
|
||||
event.preventDefault()
|
||||
moveHighlight(-1)
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Enter" && highlightedResultId) {
|
||||
event.preventDefault()
|
||||
const selected = searchResults.find((r) => r.id === highlightedResultId)
|
||||
if (selected) {
|
||||
handleSelectResult(selected)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
setIsSearchFocused(false)
|
||||
setHighlightedResultId(undefined)
|
||||
return
|
||||
}
|
||||
},
|
||||
[handleSelectResult, highlightedResultId, moveHighlight, searchResults],
|
||||
)
|
||||
|
||||
// Reset highlight based on focus and available results
|
||||
useEffect(() => {
|
||||
if (!isSearchFocused || !searchResults.length) {
|
||||
setHighlightedResultId(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
setHighlightedResultId((current) =>
|
||||
current && searchResults.some((r) => r.id === current) ? current : searchResults[0]?.id,
|
||||
)
|
||||
}, [isSearchFocused, searchResults])
|
||||
|
||||
// Ensure highlighted search result stays visible within dropdown
|
||||
useEffect(() => {
|
||||
if (!highlightedResultId || !isSearchFocused) return
|
||||
|
||||
const element = document.getElementById(`settings-search-result-${highlightedResultId}`)
|
||||
element?.scrollIntoView({ block: "nearest" })
|
||||
}, [highlightedResultId, isSearchFocused])
|
||||
|
||||
return (
|
||||
<Tab>
|
||||
<TabHeader className="flex justify-between items-center gap-2">
|
||||
<div className="flex items-center gap-2 grow">
|
||||
<div className="flex items-center gap-2 grow truncate">
|
||||
<StandardTooltip content={t("settings:header.doneButtonTooltip")}>
|
||||
<Button variant="ghost" className="px-1.5 -ml-2" onClick={() => checkUnsaveChanges(onDone)}>
|
||||
<ArrowLeft />
|
||||
|
|
@ -614,19 +682,21 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
onChange={setSearchQuery}
|
||||
onFocus={() => setIsSearchFocused(true)}
|
||||
onBlur={() => setTimeout(() => setIsSearchFocused(false), 200)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
/>
|
||||
{searchQuery && isSearchFocused && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1 bg-vscode-dropdown-background border border-vscode-dropdown-border rounded shadow-lg z-50">
|
||||
<div className="absolute top-full w-full min-w-50 right-0 mt-1 bg-vscode-dropdown-background border border-vscode-dropdown-border rounded shadow-lg z-50">
|
||||
<SettingsSearchResults
|
||||
results={searchResults}
|
||||
query={searchQuery}
|
||||
onSelectResult={handleSelectResult}
|
||||
sections={sections}
|
||||
highlightedResultId={highlightedResultId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 flex-shrink-0">
|
||||
<div className="flex gap-2 shrink-0">
|
||||
<StandardTooltip
|
||||
content={
|
||||
!isSettingValid
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react"
|
||||
import { vi, describe, it, beforeEach } from "vitest"
|
||||
|
||||
import SettingsView from "../SettingsView"
|
||||
import type { SearchResult } from "@/hooks/useSettingsSearch"
|
||||
import type { SectionName } from "@/utils/parseSettingsI18nKeys"
|
||||
|
||||
const mockUseExtensionState = vi.fn()
|
||||
const mockUseSettingsSearch = vi.fn<(query: string) => SearchResult[]>()
|
||||
|
||||
// Minimal ResizeObserver polyfill for jsdom
|
||||
class ResizeObserverPolyfill {
|
||||
callback: ResizeObserverCallback
|
||||
constructor(callback: ResizeObserverCallback) {
|
||||
this.callback = callback
|
||||
}
|
||||
observe() {
|
||||
// no-op
|
||||
}
|
||||
disconnect() {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
const defaultExtensionState = {
|
||||
currentApiConfigName: "default",
|
||||
listApiConfigMeta: [],
|
||||
uriScheme: "vscode",
|
||||
settingsImportedAt: undefined as number | undefined,
|
||||
apiConfiguration: {},
|
||||
alwaysAllowReadOnly: false,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: false,
|
||||
allowedCommands: [] as string[],
|
||||
deniedCommands: [] as string[],
|
||||
allowedMaxRequests: undefined as number | undefined,
|
||||
allowedMaxCost: undefined as number | undefined,
|
||||
language: "en",
|
||||
alwaysAllowBrowser: false,
|
||||
alwaysAllowExecute: false,
|
||||
alwaysAllowMcp: false,
|
||||
alwaysAllowModeSwitch: false,
|
||||
alwaysAllowSubtasks: false,
|
||||
alwaysAllowWrite: false,
|
||||
alwaysAllowWriteOutsideWorkspace: false,
|
||||
alwaysAllowWriteProtected: false,
|
||||
autoCondenseContext: false,
|
||||
autoCondenseContextPercent: 50,
|
||||
browserToolEnabled: true,
|
||||
browserViewportSize: "900x600",
|
||||
enableCheckpoints: false,
|
||||
checkpointTimeout: 15,
|
||||
diffEnabled: true,
|
||||
experiments: {},
|
||||
fuzzyMatchThreshold: 1,
|
||||
maxOpenTabsContext: 20,
|
||||
maxWorkspaceFiles: 200,
|
||||
mcpEnabled: false,
|
||||
remoteBrowserHost: "",
|
||||
screenshotQuality: 75,
|
||||
soundEnabled: false,
|
||||
ttsEnabled: false,
|
||||
ttsSpeed: 1,
|
||||
soundVolume: 0.5,
|
||||
telemetrySetting: "unset" as const,
|
||||
terminalOutputLineLimit: 500,
|
||||
terminalOutputCharacterLimit: 50000,
|
||||
terminalShellIntegrationTimeout: 3000,
|
||||
terminalShellIntegrationDisabled: false,
|
||||
terminalCommandDelay: 0,
|
||||
terminalPowershellCounter: false,
|
||||
terminalZshClearEolMark: false,
|
||||
terminalZshOhMy: false,
|
||||
terminalZshP10k: false,
|
||||
terminalZdotdir: false,
|
||||
writeDelayMs: 0,
|
||||
showRooIgnoredFiles: true,
|
||||
enableSubfolderRules: false,
|
||||
remoteBrowserEnabled: false,
|
||||
maxReadFileLine: -1,
|
||||
maxImageFileSize: 5,
|
||||
maxTotalImageSize: 20,
|
||||
terminalCompressProgressBar: false,
|
||||
maxConcurrentFileReads: 5,
|
||||
condensingApiConfigId: "",
|
||||
customCondensingPrompt: "",
|
||||
customSupportPrompts: {},
|
||||
profileThresholds: {},
|
||||
alwaysAllowFollowupQuestions: false,
|
||||
followupAutoApproveTimeoutMs: undefined as number | undefined,
|
||||
includeDiagnosticMessages: true,
|
||||
maxDiagnosticMessages: 50,
|
||||
includeTaskHistoryInEnhance: true,
|
||||
imageGenerationProvider: "openrouter",
|
||||
openRouterImageApiKey: "",
|
||||
openRouterImageGenerationSelectedModel: "",
|
||||
reasoningBlockCollapsed: true,
|
||||
enterBehavior: "send" as const,
|
||||
includeCurrentTime: true,
|
||||
includeCurrentCost: true,
|
||||
maxGitStatusFiles: 0,
|
||||
}
|
||||
|
||||
const mockSearchResults: SearchResult[] = [
|
||||
{
|
||||
id: "browser.enable",
|
||||
tab: "browser" as SectionName,
|
||||
labelKey: "settings:browser.enable.label",
|
||||
descriptionKey: "settings:browser.enable.description",
|
||||
translatedLabel: "Enable browser tool",
|
||||
translatedDescription: "Allows Roo to use a browser",
|
||||
matchScore: 10,
|
||||
},
|
||||
{
|
||||
id: "browser.viewport",
|
||||
tab: "browser" as SectionName,
|
||||
labelKey: "settings:browser.viewport.label",
|
||||
descriptionKey: "settings:browser.viewport.description",
|
||||
translatedLabel: "Browser viewport",
|
||||
translatedDescription: "Configure viewport size",
|
||||
matchScore: 9,
|
||||
},
|
||||
]
|
||||
|
||||
vi.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => mockUseExtensionState(),
|
||||
}))
|
||||
|
||||
vi.mock("@/hooks/useSettingsSearch", () => ({
|
||||
useSettingsSearch: (query: string) => mockUseSettingsSearch(query),
|
||||
}))
|
||||
|
||||
vi.mock("@/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({ t: (key: string) => key }),
|
||||
}))
|
||||
|
||||
vi.mock("@/components/ui", () => ({
|
||||
Button: ({ children, onClick, disabled, "data-testid": dataTestId }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} data-testid={dataTestId}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
StandardTooltip: ({ children }: any) => <>{children}</>,
|
||||
Input: ({ value, onChange, onFocus, onBlur, onKeyDown, "data-testid": dataTestId }: any) => (
|
||||
<input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
),
|
||||
AlertDialog: ({ children }: any) => <div>{children}</div>,
|
||||
AlertDialogContent: ({ children }: any) => <div>{children}</div>,
|
||||
AlertDialogTitle: ({ children }: any) => <div>{children}</div>,
|
||||
AlertDialogDescription: ({ children }: any) => <div>{children}</div>,
|
||||
AlertDialogCancel: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
|
||||
AlertDialogAction: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
|
||||
AlertDialogHeader: ({ children }: any) => <div>{children}</div>,
|
||||
AlertDialogFooter: ({ children }: any) => <div>{children}</div>,
|
||||
Tooltip: ({ children }: any) => <>{children}</>,
|
||||
TooltipContent: ({ children }: any) => <div>{children}</div>,
|
||||
TooltipProvider: ({ children }: any) => <>{children}</>,
|
||||
TooltipTrigger: ({ children, onClick }: any) => <div onClick={onClick}>{children}</div>,
|
||||
}))
|
||||
|
||||
vi.mock("../../common/Tab", () => ({
|
||||
Tab: ({ children }: any) => <div>{children}</div>,
|
||||
TabHeader: ({ children }: any) => <div>{children}</div>,
|
||||
TabContent: ({ children }: any) => <div>{children}</div>,
|
||||
TabList: ({ children }: any) => <div>{children}</div>,
|
||||
TabTrigger: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
|
||||
}))
|
||||
|
||||
vi.mock("../ApiConfigManager", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div>ApiConfigManager</div>,
|
||||
}))
|
||||
|
||||
vi.mock("../ApiOptions", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div>ApiOptions</div>,
|
||||
}))
|
||||
|
||||
// Mock all settings subsections to inert components
|
||||
vi.mock("../AutoApproveSettings", () => ({ AutoApproveSettings: () => <div>AutoApproveSettings</div> }))
|
||||
vi.mock("../BrowserSettings", () => ({ BrowserSettings: () => <div>BrowserSettings</div> }))
|
||||
vi.mock("../CheckpointSettings", () => ({ CheckpointSettings: () => <div>CheckpointSettings</div> }))
|
||||
vi.mock("../NotificationSettings", () => ({ NotificationSettings: () => <div>NotificationSettings</div> }))
|
||||
vi.mock("../ContextManagementSettings", () => ({
|
||||
ContextManagementSettings: () => <div>ContextManagementSettings</div>,
|
||||
}))
|
||||
vi.mock("../TerminalSettings", () => ({ TerminalSettings: () => <div>TerminalSettings</div> }))
|
||||
vi.mock("../ExperimentalSettings", () => ({ ExperimentalSettings: () => <div>ExperimentalSettings</div> }))
|
||||
vi.mock("../LanguageSettings", () => ({ LanguageSettings: () => <div>LanguageSettings</div> }))
|
||||
vi.mock("../About", () => ({ About: () => <div>About</div> }))
|
||||
vi.mock("../PromptsSettings", () => ({ __esModule: true, default: () => <div>PromptsSettings</div> }))
|
||||
vi.mock("../SlashCommandsSettings", () => ({ SlashCommandsSettings: () => <div>SlashCommandsSettings</div> }))
|
||||
vi.mock("../UISettings", () => ({ UISettings: () => <div>UISettings</div> }))
|
||||
|
||||
describe("SettingsView search interactions", () => {
|
||||
beforeEach(() => {
|
||||
;(global as any).ResizeObserver = (global as any).ResizeObserver || ResizeObserverPolyfill
|
||||
mockUseExtensionState.mockReturnValue(defaultExtensionState)
|
||||
mockUseSettingsSearch.mockImplementation(() => mockSearchResults)
|
||||
})
|
||||
|
||||
it("allows clicking a search result without closing before selection", async () => {
|
||||
render(<SettingsView onDone={vi.fn()} />)
|
||||
|
||||
const input = screen.getByTestId("settings-search-input") as HTMLInputElement
|
||||
fireEvent.focus(input)
|
||||
fireEvent.change(input, { target: { value: "browser" } })
|
||||
|
||||
const listbox = await screen.findByRole("listbox")
|
||||
expect(listbox).toBeInTheDocument()
|
||||
|
||||
const options = screen.getAllByRole("option")
|
||||
fireEvent.mouseDown(options[0])
|
||||
fireEvent.click(options[0])
|
||||
|
||||
expect(input.value).toBe("")
|
||||
expect(screen.queryByRole("listbox")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("supports keyboard navigation and enter selection from search input", async () => {
|
||||
render(<SettingsView onDone={vi.fn()} />)
|
||||
|
||||
const input = screen.getByTestId("settings-search-input") as HTMLInputElement
|
||||
fireEvent.focus(input)
|
||||
fireEvent.change(input, { target: { value: "browser" } })
|
||||
|
||||
await screen.findByRole("listbox")
|
||||
let options = screen.getAllByRole("option")
|
||||
expect(options[0]).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
fireEvent.keyDown(input, { key: "ArrowDown" })
|
||||
options = screen.getAllByRole("option")
|
||||
expect(options[1]).toHaveAttribute("aria-selected", "true")
|
||||
|
||||
fireEvent.keyDown(input, { key: "Enter" })
|
||||
expect(screen.queryByRole("listbox")).not.toBeInTheDocument()
|
||||
expect(input.value).toBe("")
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue