Skip to content

Commit 883cb92

Browse files
authored
refactor: general UI improvements (#2987)
1 parent 2e2c0a8 commit 883cb92

File tree

24 files changed

+403
-490
lines changed

24 files changed

+403
-490
lines changed

webview-ui/src/components/prompts/PromptsView.tsx

Lines changed: 276 additions & 393 deletions
Large diffs are not rendered by default.

webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ jest.mock("@src/utils/vscode", () => ({
1010
},
1111
}))
1212

13+
// Mock all lucide-react icons with a proxy to handle any icon requested
14+
jest.mock("lucide-react", () => {
15+
return new Proxy(
16+
{},
17+
{
18+
get: function (obj, prop) {
19+
// Return a component factory for any icon that's requested
20+
if (prop === "__esModule") {
21+
return true
22+
}
23+
return () => <div data-testid={`${String(prop)}-icon`}>{String(prop)}</div>
24+
},
25+
},
26+
)
27+
})
28+
1329
const mockExtensionState = {
1430
customModePrompts: {},
1531
listApiConfigMeta: [
@@ -19,6 +35,9 @@ const mockExtensionState = {
1935
enhancementApiConfigId: "",
2036
setEnhancementApiConfigId: jest.fn(),
2137
mode: "code",
38+
customModes: [],
39+
customSupportPrompts: [],
40+
currentApiConfigName: "",
2241
customInstructions: "Initial instructions",
2342
setCustomInstructions: jest.fn(),
2443
}
@@ -32,69 +51,67 @@ const renderPromptsView = (props = {}) => {
3251
)
3352
}
3453

54+
class MockResizeObserver {
55+
observe() {}
56+
unobserve() {}
57+
disconnect() {}
58+
}
59+
60+
global.ResizeObserver = MockResizeObserver
61+
62+
Element.prototype.scrollIntoView = jest.fn()
63+
3564
describe("PromptsView", () => {
3665
beforeEach(() => {
3766
jest.clearAllMocks()
3867
})
3968

40-
it("renders all mode tabs", () => {
41-
renderPromptsView()
42-
expect(screen.getByTestId("code-tab")).toBeInTheDocument()
43-
expect(screen.getByTestId("ask-tab")).toBeInTheDocument()
44-
expect(screen.getByTestId("architect-tab")).toBeInTheDocument()
69+
it("displays the current mode name in the select trigger", () => {
70+
renderPromptsView({ mode: "code" })
71+
const selectTrigger = screen.getByTestId("mode-select-trigger")
72+
expect(selectTrigger).toHaveTextContent("Code")
4573
})
4674

47-
it("defaults to current mode as active tab", () => {
48-
renderPromptsView({ mode: "ask" })
49-
50-
const codeTab = screen.getByTestId("code-tab")
51-
const askTab = screen.getByTestId("ask-tab")
52-
const architectTab = screen.getByTestId("architect-tab")
53-
54-
expect(askTab).toHaveAttribute("data-active", "true")
55-
expect(codeTab).toHaveAttribute("data-active", "false")
56-
expect(architectTab).toHaveAttribute("data-active", "false")
75+
it("opens the mode selection popover when the trigger is clicked", async () => {
76+
renderPromptsView()
77+
const selectTrigger = screen.getByTestId("mode-select-trigger")
78+
fireEvent.click(selectTrigger)
79+
await waitFor(() => {
80+
expect(selectTrigger).toHaveAttribute("aria-expanded", "true")
81+
})
5782
})
5883

59-
it("switches between tabs correctly", async () => {
60-
const { rerender } = render(
61-
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "code" } as any}>
62-
<PromptsView onDone={jest.fn()} />
63-
</ExtensionStateContext.Provider>,
64-
)
65-
66-
const codeTab = screen.getByTestId("code-tab")
67-
const askTab = screen.getByTestId("ask-tab")
68-
const architectTab = screen.getByTestId("architect-tab")
84+
it("filters mode options based on search input", async () => {
85+
renderPromptsView()
86+
const selectTrigger = screen.getByTestId("mode-select-trigger")
87+
fireEvent.click(selectTrigger)
6988

70-
// Initial state matches current mode (code)
71-
expect(codeTab).toHaveAttribute("data-active", "true")
72-
expect(askTab).toHaveAttribute("data-active", "false")
73-
expect(architectTab).toHaveAttribute("data-active", "false")
89+
const searchInput = screen.getByTestId("mode-search-input")
90+
fireEvent.change(searchInput, { target: { value: "ask" } })
7491

75-
// Click Ask tab and update context
76-
fireEvent.click(askTab)
77-
rerender(
78-
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "ask" } as any}>
79-
<PromptsView onDone={jest.fn()} />
80-
</ExtensionStateContext.Provider>,
81-
)
92+
await waitFor(() => {
93+
expect(screen.getByTestId("mode-option-ask")).toBeInTheDocument()
94+
expect(screen.queryByTestId("mode-option-code")).not.toBeInTheDocument()
95+
expect(screen.queryByTestId("mode-option-architect")).not.toBeInTheDocument()
96+
})
97+
})
8298

83-
expect(askTab).toHaveAttribute("data-active", "true")
84-
expect(codeTab).toHaveAttribute("data-active", "false")
85-
expect(architectTab).toHaveAttribute("data-active", "false")
99+
it("selects a mode from the dropdown and sends update message", async () => {
100+
renderPromptsView()
101+
const selectTrigger = screen.getByTestId("mode-select-trigger")
102+
fireEvent.click(selectTrigger)
86103

87-
// Click Architect tab and update context
88-
fireEvent.click(architectTab)
89-
rerender(
90-
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "architect" } as any}>
91-
<PromptsView onDone={jest.fn()} />
92-
</ExtensionStateContext.Provider>,
93-
)
104+
const askOption = await waitFor(() => screen.getByTestId("mode-option-ask"))
105+
fireEvent.click(askOption)
94106

95-
expect(architectTab).toHaveAttribute("data-active", "true")
96-
expect(askTab).toHaveAttribute("data-active", "false")
97-
expect(codeTab).toHaveAttribute("data-active", "false")
107+
expect(mockExtensionState.setEnhancementApiConfigId).not.toHaveBeenCalled() // Ensure this is not called by mode switch
108+
expect(vscode.postMessage).toHaveBeenCalledWith({
109+
type: "mode",
110+
text: "ask",
111+
})
112+
await waitFor(() => {
113+
expect(selectTrigger).toHaveAttribute("aria-expanded", "false")
114+
})
98115
})
99116

100117
it("handles prompt changes correctly", async () => {
@@ -159,21 +176,19 @@ describe("PromptsView", () => {
159176
it("handles API configuration selection", async () => {
160177
renderPromptsView()
161178

162-
// Click the ENHANCE tab first to show the API config dropdown
163-
const enhanceTab = screen.getByTestId("ENHANCE-tab")
164-
fireEvent.click(enhanceTab)
179+
const trigger = screen.getByTestId("support-prompt-select-trigger")
180+
fireEvent.click(trigger)
165181

166-
// Wait for the ENHANCE tab click to take effect
167-
const dropdown = await waitFor(() => screen.getByTestId("api-config-dropdown"))
168-
fireEvent.change(dropdown, {
169-
target: { value: "config1" },
170-
})
182+
const enhanceOption = await waitFor(() => screen.getByTestId("ENHANCE-option"))
183+
fireEvent.click(enhanceOption)
171184

172-
expect(mockExtensionState.setEnhancementApiConfigId).toHaveBeenCalledWith("config1")
173-
expect(vscode.postMessage).toHaveBeenCalledWith({
174-
type: "enhancementApiConfigId",
175-
text: "config1",
176-
})
185+
const apiConfig = await waitFor(() => screen.getByTestId("api-config-select"))
186+
fireEvent.click(apiConfig)
187+
188+
const config1 = await waitFor(() => screen.getByTestId("config1-option"))
189+
fireEvent.click(config1)
190+
191+
expect(mockExtensionState.setEnhancementApiConfigId).toHaveBeenCalledWith("config1") // Ensure this is not called by mode switch
177192
})
178193

179194
it("handles clearing custom instructions correctly", async () => {

webview-ui/src/components/settings/AutoApproveSettings.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { HTMLAttributes, useState } from "react"
22
import { X } from "lucide-react"
33

44
import { useAppTranslation } from "@/i18n/TranslationContext"
5-
import { VSCodeTextField, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
5+
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
66
import { vscode } from "@/utils/vscode"
7-
import { Button, Slider } from "@/components/ui"
7+
import { Button, Input, Slider } from "@/components/ui"
88

99
import { SetCachedStateField } from "./types"
1010
import { SectionHeader } from "./SectionHeader"
@@ -203,9 +203,9 @@ export const AutoApproveSettings = ({
203203
</div>
204204

205205
<div className="flex gap-2">
206-
<VSCodeTextField
206+
<Input
207207
value={commandInput}
208-
onInput={(e: any) => setCommandInput(e.target.value)}
208+
onChange={(e: any) => setCommandInput(e.target.value)}
209209
onKeyDown={(e: any) => {
210210
if (e.key === "Enter") {
211211
e.preventDefault()
@@ -216,7 +216,7 @@ export const AutoApproveSettings = ({
216216
className="grow"
217217
data-testid="command-input"
218218
/>
219-
<Button onClick={handleAddCommand} data-testid="add-command-button">
219+
<Button className="h-8" onClick={handleAddCommand} data-testid="add-command-button">
220220
{t("settings:autoApprove.execute.addButton")}
221221
</Button>
222222
</div>
@@ -234,7 +234,7 @@ export const AutoApproveSettings = ({
234234
}}>
235235
<div className="flex flex-row items-center gap-1">
236236
<div>{cmd}</div>
237-
<X className="text-primary-foreground scale-75" />
237+
<X className="text-foreground scale-75" />
238238
</div>
239239
</Button>
240240
))}

webview-ui/src/components/settings/ContextManagementSettings.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
44
import { Database } from "lucide-react"
55

66
import { cn } from "@/lib/utils"
7-
import { Slider } from "@/components/ui"
7+
import { Input, Slider } from "@/components/ui"
88

99
import { SetCachedStateField } from "./types"
1010
import { SectionHeader } from "./SectionHeader"
@@ -96,7 +96,7 @@ export const ContextManagementSettings = ({
9696
<div className="flex flex-col gap-2">
9797
<span className="font-medium">{t("settings:contextManagement.maxReadFile.label")}</span>
9898
<div className="flex items-center gap-4">
99-
<input
99+
<Input
100100
type="number"
101101
pattern="-?[0-9]*"
102102
className="w-24 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:opacity-50"

webview-ui/src/components/ui/alert-dialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof
9090
<AlertDialogPrimitive.Action
9191
className={cn(
9292
buttonVariants(),
93-
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground border border-transparent h-6 px-3 py-1",
93+
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground h-6 px-3 py-1 border",
9494
className,
9595
)}
9696
{...props}
@@ -103,7 +103,7 @@ function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof
103103
<AlertDialogPrimitive.Cancel
104104
className={cn(
105105
buttonVariants({ variant: "outline" }),
106-
"bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground border border-vscode-button-border h-6 px-3 py-1",
106+
"bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground h-6 px-3 py-1 border",
107107
className,
108108
)}
109109
{...props}

webview-ui/src/components/ui/button.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ const buttonVariants = cva(
99
{
1010
variants: {
1111
variant: {
12-
default:
13-
"border border-vscode-input-border bg-primary text-primary-foreground shadow hover:bg-primary/90",
14-
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
12+
default: "border border-vscode-input-border bg-primary text-primary-foreground hover:bg-primary/90",
13+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
1514
outline:
16-
"border border-vscode-input-border bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
15+
"border border-vscode-input-border bg-transparent hover:bg-accent hover:text-accent-foreground",
1716
secondary:
18-
"border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
17+
"border border-vscode-input-border bg-secondary text-secondary-foreground hover:bg-secondary/80",
1918
ghost: "hover:bg-accent hover:text-accent-foreground",
2019
link: "text-primary underline-offset-4 hover:underline",
2120
combobox:

webview-ui/src/components/ui/slider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ const Slider = React.forwardRef<
1111
ref={ref}
1212
className={cn("relative flex w-full touch-none select-none items-center", className)}
1313
{...props}>
14-
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-accent border border-[#767676] dark:border-[#858585] rounded-sm">
14+
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-accent rounded-sm border">
1515
<SliderPrimitive.Range className="absolute h-full bg-vscode-button-background" />
1616
</SliderPrimitive.Track>
17-
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-primary/50 bg-primary shadow transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
17+
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-primary/50 bg-vscode-button-background transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
1818
</SliderPrimitive.Root>
1919
))
2020
Slider.displayName = SliderPrimitive.Root.displayName

webview-ui/src/components/ui/textarea.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"tex
77
return (
88
<textarea
99
className={cn(
10-
"flex min-h-[60px] w-full rounded-xs px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
11-
"border-[var(--vscode-input-border,var(--vscode-input-background))] focus-visible:border-vscode-focusBorder",
10+
"flex min-h-[60px] w-full rounded-xs px-3 py-2 text-base placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
11+
"border border-[var(--vscode-input-border,var(--vscode-input-background))] focus-visible:border-vscode-focusBorder",
1212
"bg-vscode-input-background",
1313
"text-vscode-input-foreground",
1414
className,

webview-ui/src/i18n/locales/ca/prompts.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"editModesConfig": "Editar configuració de modes",
88
"editGlobalModes": "Editar modes globals",
99
"editProjectModes": "Editar modes de projecte (.roomodes)",
10-
"createModeHelpText": "Feu clic a + per crear un nou mode personalitzat, o simplement demaneu a Roo al xat que en creï un per a vostè!"
10+
"createModeHelpText": "Feu clic a + per crear un nou mode personalitzat, o simplement demaneu a Roo al xat que en creï un per a vostè!",
11+
"selectMode": "Cerqueu modes"
1112
},
1213
"apiConfiguration": {
1314
"title": "Configuració d'API",

webview-ui/src/i18n/locales/de/prompts.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"editModesConfig": "Moduskonfiguration bearbeiten",
88
"editGlobalModes": "Globale Modi bearbeiten",
99
"editProjectModes": "Projektmodi bearbeiten (.roomodes)",
10-
"createModeHelpText": "Klicke auf +, um einen neuen benutzerdefinierten Modus zu erstellen, oder bitte Roo einfach im Chat, einen für dich zu erstellen!"
10+
"createModeHelpText": "Klicke auf +, um einen neuen benutzerdefinierten Modus zu erstellen, oder bitte Roo einfach im Chat, einen für dich zu erstellen!",
11+
"selectMode": "Modi suchen"
1112
},
1213
"apiConfiguration": {
1314
"title": "API-Konfiguration",

0 commit comments

Comments
 (0)