This repository was archived by the owner on May 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathApiConfigManager.spec.tsx
More file actions
354 lines (290 loc) · 12.2 KB
/
Copy pathApiConfigManager.spec.tsx
File metadata and controls
354 lines (290 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// npx vitest src/components/settings/__tests__/ApiConfigManager.spec.tsx
import { render, screen, fireEvent, within } from "@/utils/test-utils"
import ApiConfigManager from "../ApiConfigManager"
// Mock VSCode components
vitest.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeTextField: ({ value, onInput, placeholder, onKeyDown, "data-testid": dataTestId }: any) => (
<input
value={value}
onChange={(e) => onInput(e)}
placeholder={placeholder}
onKeyDown={onKeyDown}
data-testid={dataTestId}
ref={undefined} // Explicitly set ref to undefined to avoid warning
/>
),
}))
vitest.mock("@/components/ui", () => ({
...vitest.importActual("@/components/ui"),
Dialog: ({ children, open }: any) => (
<div role="dialog" aria-modal="true" style={{ display: open ? "block" : "none" }} data-testid="dialog">
{children}
</div>
),
DialogContent: ({ children }: any) => <div data-testid="dialog-content">{children}</div>,
DialogTitle: ({ children }: any) => <div data-testid="dialog-title">{children}</div>,
Button: ({ children, onClick, disabled, "data-testid": dataTestId }: any) => (
<button onClick={onClick} disabled={disabled} data-testid={dataTestId}>
{children}
</button>
),
Input: ({ value, onInput, placeholder, onKeyDown, "data-testid": dataTestId }: any) => (
<input
value={value}
onChange={(e) => onInput(e)}
placeholder={placeholder}
onKeyDown={onKeyDown}
data-testid={dataTestId}
/>
),
StandardTooltip: ({ children, content }: any) => <div title={content}>{children}</div>,
// New components for searchable dropdown
Popover: ({ children, open }: any) => (
<div className="popover" style={{ position: "relative" }}>
{children}
{open && <div className="popover-content" style={{ position: "absolute", top: "100%", left: 0 }}></div>}
</div>
),
PopoverTrigger: ({ children }: any) => <div className="popover-trigger">{children}</div>,
PopoverContent: ({ children }: any) => <div className="popover-content">{children}</div>,
Command: ({ children }: any) => <div className="command">{children}</div>,
CommandInput: ({ value, onValueChange, placeholder, className, "data-testid": dataTestId }: any) => (
<input
value={value}
onChange={(e) => onValueChange(e.target.value)}
placeholder={placeholder}
className={className}
data-testid={dataTestId}
/>
),
CommandList: ({ children }: any) => <div className="command-list">{children}</div>,
CommandEmpty: ({ children }: any) => (children ? <div className="command-empty">{children}</div> : null),
CommandGroup: ({ children }: any) => <div className="command-group">{children}</div>,
CommandItem: ({ children, value, onSelect }: any) => (
<div className="command-item" onClick={() => onSelect(value)} data-value={value}>
{children}
</div>
),
// Keep old components for backward compatibility
Select: ({ value, onValueChange }: any) => (
<select
value={value}
onChange={(e) => {
if (onValueChange) onValueChange(e.target.value)
}}
data-testid="select-component">
<option value="Default Config">Default Config</option>
<option value="Another Config">Another Config</option>
</select>
),
SelectTrigger: ({ children }: any) => <div className="select-trigger-mock">{children}</div>,
SelectValue: ({ children }: any) => <div className="select-value-mock">{children}</div>,
SelectContent: ({ children }: any) => <div className="select-content-mock">{children}</div>,
SelectItem: ({ children, value }: any) => (
<option value={value} className="select-item-mock">
{children}
</option>
),
SearchableSelect: ({ value, onValueChange, options, placeholder, "data-testid": dataTestId }: any) => (
<select
value={value}
onChange={(e) => {
if (onValueChange) onValueChange(e.target.value)
}}
data-testid={dataTestId || "select-component"}>
<option value="">{placeholder || "settings:common.select"}</option>
{options?.map((option: any) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
),
DropdownMenu: ({ children }: any) => <div data-testid="dropdown-menu">{children}</div>,
DropdownMenuTrigger: ({ children }: any) => <div data-testid="dropdown-trigger">{children}</div>,
DropdownMenuContent: ({ children }: any) => <div data-testid="dropdown-content">{children}</div>,
DropdownMenuCheckboxItem: ({ children, checked, onCheckedChange, "data-testid": dataTestId }: any) => (
<div data-testid={dataTestId} data-checked={checked} onClick={() => onCheckedChange?.(!checked)}>
{children}
</div>
),
}))
describe("ApiConfigManager", () => {
const mockOnSelectConfig = vitest.fn()
const mockOnDeleteConfig = vitest.fn()
const mockOnRenameConfig = vitest.fn()
const mockOnUpsertConfig = vitest.fn()
const defaultProps = {
currentApiConfigName: "Default Config",
listApiConfigMeta: [
{ id: "default", name: "Default Config" },
{ id: "another", name: "Another Config" },
],
onSelectConfig: mockOnSelectConfig,
onDeleteConfig: mockOnDeleteConfig,
onRenameConfig: mockOnRenameConfig,
onUpsertConfig: mockOnUpsertConfig,
}
beforeEach(() => {
vitest.clearAllMocks()
})
const getRenameForm = () => screen.getByTestId("rename-form")
const getDialogContent = () => screen.getByTestId("dialog-content")
it("opens new profile dialog when clicking add button", () => {
render(<ApiConfigManager {...defaultProps} />)
const addButton = screen.getByTestId("add-profile-button")
fireEvent.click(addButton)
expect(screen.getByTestId("dialog")).toBeVisible()
expect(screen.getByTestId("dialog-title")).toHaveTextContent("settings:providers.newProfile")
})
it("creates new profile with entered name", () => {
render(<ApiConfigManager {...defaultProps} />)
// Open dialog
const addButton = screen.getByTestId("add-profile-button")
fireEvent.click(addButton)
// Enter new profile name
const input = screen.getByTestId("new-profile-input")
fireEvent.input(input, { target: { value: "New Profile" } })
// Click create button
const createButton = screen.getByText("settings:providers.createProfile")
fireEvent.click(createButton)
expect(mockOnUpsertConfig).toHaveBeenCalledWith("New Profile")
})
it("shows error when creating profile with existing name", () => {
render(<ApiConfigManager {...defaultProps} />)
// Open dialog
const addButton = screen.getByTestId("add-profile-button")
fireEvent.click(addButton)
// Enter existing profile name
const input = screen.getByTestId("new-profile-input")
fireEvent.input(input, { target: { value: "Default Config" } })
// Click create button to trigger validation
const createButton = screen.getByText("settings:providers.createProfile")
fireEvent.click(createButton)
// Verify error message
const dialogContent = getDialogContent()
const errorMessage = within(dialogContent).getByTestId("error-message")
expect(errorMessage).toHaveTextContent("settings:providers.nameExists")
expect(mockOnUpsertConfig).not.toHaveBeenCalled()
})
it("prevents creating profile with empty name", () => {
render(<ApiConfigManager {...defaultProps} />)
// Open dialog
const addButton = screen.getByTestId("add-profile-button")
fireEvent.click(addButton)
// Enter empty name
const input = screen.getByTestId("new-profile-input")
fireEvent.input(input, { target: { value: " " } })
// Verify create button is disabled
const createButton = screen.getByText("settings:providers.createProfile")
expect(createButton).toBeDisabled()
expect(mockOnUpsertConfig).not.toHaveBeenCalled()
})
it("allows renaming the current config", () => {
render(<ApiConfigManager {...defaultProps} />)
// Start rename
const renameButton = screen.getByTestId("rename-profile-button")
fireEvent.click(renameButton)
// Find input and enter new name
const input = screen.getByDisplayValue("Default Config")
fireEvent.input(input, { target: { value: "New Name" } })
// Save
const saveButton = screen.getByTestId("save-rename-button")
fireEvent.click(saveButton)
expect(mockOnRenameConfig).toHaveBeenCalledWith("Default Config", "New Name")
})
it("shows error when renaming to existing config name", () => {
render(<ApiConfigManager {...defaultProps} />)
// Start rename
const renameButton = screen.getByTestId("rename-profile-button")
fireEvent.click(renameButton)
// Find input and enter existing name
const input = screen.getByDisplayValue("Default Config")
fireEvent.input(input, { target: { value: "Another Config" } })
// Save to trigger validation
const saveButton = screen.getByTestId("save-rename-button")
fireEvent.click(saveButton)
// Verify error message
const renameForm = getRenameForm()
const errorMessage = within(renameForm).getByTestId("error-message")
expect(errorMessage).toHaveTextContent("settings:providers.nameExists")
expect(mockOnRenameConfig).not.toHaveBeenCalled()
})
it("prevents renaming to empty name", () => {
render(<ApiConfigManager {...defaultProps} />)
// Start rename
const renameButton = screen.getByTestId("rename-profile-button")
fireEvent.click(renameButton)
// Find input and enter empty name
const input = screen.getByDisplayValue("Default Config")
fireEvent.input(input, { target: { value: " " } })
// Verify save button is disabled
const saveButton = screen.getByTestId("save-rename-button")
expect(saveButton).toBeDisabled()
expect(mockOnRenameConfig).not.toHaveBeenCalled()
})
it("allows selecting a different config", () => {
render(<ApiConfigManager {...defaultProps} />)
// The SearchableSelect mock renders as a simple select element
const selectElement = screen.getByTestId("select-component") as HTMLSelectElement
// Change the select value to "Another Config"
fireEvent.change(selectElement, { target: { value: "Another Config" } })
expect(mockOnSelectConfig).toHaveBeenCalledWith("Another Config")
})
it("allows deleting the current config when not the only one", () => {
render(<ApiConfigManager {...defaultProps} />)
const deleteButton = screen.getByTestId("delete-profile-button")
expect(deleteButton).not.toBeDisabled()
fireEvent.click(deleteButton)
expect(mockOnDeleteConfig).toHaveBeenCalledWith("Default Config")
})
it("disables delete button when only one config exists", () => {
render(<ApiConfigManager {...defaultProps} listApiConfigMeta={[{ id: "default", name: "Default Config" }]} />)
const deleteButton = screen.getByTestId("delete-profile-button")
expect(deleteButton).toHaveAttribute("disabled")
})
it("cancels rename operation when clicking cancel", () => {
render(<ApiConfigManager {...defaultProps} />)
// Start rename
const renameButton = screen.getByTestId("rename-profile-button")
fireEvent.click(renameButton)
// Find input and enter new name
const input = screen.getByDisplayValue("Default Config")
fireEvent.input(input, { target: { value: "New Name" } })
// Cancel
const cancelButton = screen.getByTestId("cancel-rename-button")
fireEvent.click(cancelButton)
// Verify rename was not called
expect(mockOnRenameConfig).not.toHaveBeenCalled()
// Verify we're back to normal view
expect(screen.queryByDisplayValue("New Name")).not.toBeInTheDocument()
})
it("handles keyboard events in new profile dialog", () => {
render(<ApiConfigManager {...defaultProps} />)
// Open dialog
const addButton = screen.getByTestId("add-profile-button")
fireEvent.click(addButton)
const input = screen.getByTestId("new-profile-input")
// Test Enter key
fireEvent.input(input, { target: { value: "New Profile" } })
fireEvent.keyDown(input, { key: "Enter" })
expect(mockOnUpsertConfig).toHaveBeenCalledWith("New Profile")
// Test Escape key
fireEvent.keyDown(input, { key: "Escape" })
expect(screen.getByTestId("dialog")).not.toBeVisible()
})
it("handles keyboard events in rename mode", () => {
render(<ApiConfigManager {...defaultProps} />)
// Start rename
const renameButton = screen.getByTestId("rename-profile-button")
fireEvent.click(renameButton)
const input = screen.getByDisplayValue("Default Config")
// Test Enter key
fireEvent.input(input, { target: { value: "New Name" } })
fireEvent.keyDown(input, { key: "Enter" })
expect(mockOnRenameConfig).toHaveBeenCalledWith("Default Config", "New Name")
// Test Escape key
fireEvent.keyDown(input, { key: "Escape" })
expect(screen.queryByDisplayValue("New Name")).not.toBeInTheDocument()
})
})