Files
Roo-Code/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx
2025-01-17 14:11:28 -05:00

341 lines
9.0 KiB
TypeScript

import React from "react"
import { render, screen, fireEvent } from "@testing-library/react"
import SettingsView from "../SettingsView"
import { ExtensionStateContextProvider } from "../../../context/ExtensionStateContext"
import { vscode } from "../../../utils/vscode"
// Mock vscode API
jest.mock("../../../utils/vscode", () => ({
vscode: {
postMessage: jest.fn(),
},
}))
// Mock ApiConfigManager component
jest.mock("../ApiConfigManager", () => ({
__esModule: true,
default: ({
currentApiConfigName,
listApiConfigMeta,
onSelectConfig,
onDeleteConfig,
onRenameConfig,
onUpsertConfig,
}: any) => (
<div data-testid="api-config-management">
<span>Current config: {currentApiConfigName}</span>
</div>
),
}))
// Mock VSCode components
jest.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeButton: ({ children, onClick, appearance }: any) =>
appearance === "icon" ? (
<button onClick={onClick} className="codicon codicon-close" aria-label="Remove command">
<span className="codicon codicon-close" />
</button>
) : (
<button onClick={onClick} data-appearance={appearance}>
{children}
</button>
),
VSCodeCheckbox: ({ children, onChange, checked }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
aria-label={typeof children === "string" ? children : undefined}
/>
{children}
</label>
),
VSCodeTextField: ({ value, onInput, placeholder }: any) => (
<input
type="text"
value={value}
onChange={(e) => onInput({ target: { value: e.target.value } })}
placeholder={placeholder}
/>
),
VSCodeTextArea: () => <textarea />,
VSCodeLink: ({ children, href }: any) => <a href={href || "#"}>{children}</a>,
VSCodeDropdown: ({ children, value, onChange }: any) => (
<select value={value} onChange={onChange}>
{children}
</select>
),
VSCodeOption: ({ children, value }: any) => <option value={value}>{children}</option>,
VSCodeRadio: ({ children, value, checked, onChange }: any) => (
<input type="radio" value={value} checked={checked} onChange={onChange} />
),
VSCodeRadioGroup: ({ children, value, onChange }: any) => <div onChange={onChange}>{children}</div>,
VSCodeSlider: ({ value, onChange }: any) => (
<input
type="range"
value={value}
onChange={(e) => onChange({ target: { value: Number(e.target.value) } })}
min={0}
max={1}
step={0.01}
style={{ flexGrow: 1, height: "2px" }}
/>
),
}))
// Mock window.postMessage to trigger state hydration
const mockPostMessage = (state: any) => {
window.postMessage(
{
type: "state",
state: {
version: "1.0.0",
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
allowedCommands: [],
alwaysAllowExecute: false,
soundEnabled: false,
soundVolume: 0.5,
...state,
},
},
"*",
)
}
const renderSettingsView = () => {
const onDone = jest.fn()
render(
<ExtensionStateContextProvider>
<SettingsView onDone={onDone} />
</ExtensionStateContextProvider>,
)
// Hydrate initial state
mockPostMessage({})
return { onDone }
}
describe("SettingsView - Sound Settings", () => {
beforeEach(() => {
jest.clearAllMocks()
})
it("initializes with sound disabled by default", () => {
renderSettingsView()
const soundCheckbox = screen.getByRole("checkbox", {
name: /Enable sound effects/i,
})
expect(soundCheckbox).not.toBeChecked()
// Volume slider should not be visible when sound is disabled
expect(screen.queryByRole("slider", { name: /volume/i })).not.toBeInTheDocument()
})
it("toggles sound setting and sends message to VSCode", () => {
renderSettingsView()
const soundCheckbox = screen.getByRole("checkbox", {
name: /Enable sound effects/i,
})
// Enable sound
fireEvent.click(soundCheckbox)
expect(soundCheckbox).toBeChecked()
// Click Done to save settings
const doneButton = screen.getByText("Done")
fireEvent.click(doneButton)
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "soundEnabled",
bool: true,
}),
)
})
it("shows volume slider when sound is enabled", () => {
renderSettingsView()
// Enable sound
const soundCheckbox = screen.getByRole("checkbox", {
name: /Enable sound effects/i,
})
fireEvent.click(soundCheckbox)
// Volume slider should be visible
const volumeSlider = screen.getByRole("slider", { name: /volume/i })
expect(volumeSlider).toBeInTheDocument()
expect(volumeSlider).toHaveValue("0.5")
})
it("updates volume and sends message to VSCode when slider changes", () => {
renderSettingsView()
// Enable sound
const soundCheckbox = screen.getByRole("checkbox", {
name: /Enable sound effects/i,
})
fireEvent.click(soundCheckbox)
// Change volume
const volumeSlider = screen.getByRole("slider", { name: /volume/i })
fireEvent.change(volumeSlider, { target: { value: "0.75" } })
// Click Done to save settings
const doneButton = screen.getByText("Done")
fireEvent.click(doneButton)
// Verify message sent to VSCode
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "soundVolume",
value: 0.75,
})
})
})
describe("SettingsView - API Configuration", () => {
beforeEach(() => {
jest.clearAllMocks()
})
it("renders ApiConfigManagement with correct props", () => {
renderSettingsView()
expect(screen.getByTestId("api-config-management")).toBeInTheDocument()
})
})
describe("SettingsView - Allowed Commands", () => {
beforeEach(() => {
jest.clearAllMocks()
})
it("shows allowed commands section when alwaysAllowExecute is enabled", () => {
renderSettingsView()
// Enable always allow execute
const executeCheckbox = screen.getByRole("checkbox", {
name: /Always approve allowed execute operations/i,
})
fireEvent.click(executeCheckbox)
// Verify allowed commands section appears
expect(screen.getByText(/Allowed Auto-Execute Commands/i)).toBeInTheDocument()
expect(screen.getByPlaceholderText(/Enter command prefix/i)).toBeInTheDocument()
})
it("adds new command to the list", () => {
renderSettingsView()
// Enable always allow execute
const executeCheckbox = screen.getByRole("checkbox", {
name: /Always approve allowed execute operations/i,
})
fireEvent.click(executeCheckbox)
// Add a new command
const input = screen.getByPlaceholderText(/Enter command prefix/i)
fireEvent.change(input, { target: { value: "npm test" } })
const addButton = screen.getByText("Add")
fireEvent.click(addButton)
// Verify command was added
expect(screen.getByText("npm test")).toBeInTheDocument()
// Verify VSCode message was sent
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "allowedCommands",
commands: ["npm test"],
})
})
it("removes command from the list", () => {
renderSettingsView()
// Enable always allow execute
const executeCheckbox = screen.getByRole("checkbox", {
name: /Always approve allowed execute operations/i,
})
fireEvent.click(executeCheckbox)
// Add a command
const input = screen.getByPlaceholderText(/Enter command prefix/i)
fireEvent.change(input, { target: { value: "npm test" } })
const addButton = screen.getByText("Add")
fireEvent.click(addButton)
// Remove the command
const removeButton = screen.getByRole("button", { name: "Remove command" })
fireEvent.click(removeButton)
// Verify command was removed
expect(screen.queryByText("npm test")).not.toBeInTheDocument()
// Verify VSCode message was sent
expect(vscode.postMessage).toHaveBeenLastCalledWith({
type: "allowedCommands",
commands: [],
})
})
it("prevents duplicate commands", () => {
renderSettingsView()
// Enable always allow execute
const executeCheckbox = screen.getByRole("checkbox", {
name: /Always approve allowed execute operations/i,
})
fireEvent.click(executeCheckbox)
// Add a command twice
const input = screen.getByPlaceholderText(/Enter command prefix/i)
const addButton = screen.getByText("Add")
// First addition
fireEvent.change(input, { target: { value: "npm test" } })
fireEvent.click(addButton)
// Second addition attempt
fireEvent.change(input, { target: { value: "npm test" } })
fireEvent.click(addButton)
// Verify command appears only once
const commands = screen.getAllByText("npm test")
expect(commands).toHaveLength(1)
})
it("saves allowed commands when clicking Done", () => {
const { onDone } = renderSettingsView()
// Enable always allow execute
const executeCheckbox = screen.getByRole("checkbox", {
name: /Always approve allowed execute operations/i,
})
fireEvent.click(executeCheckbox)
// Add a command
const input = screen.getByPlaceholderText(/Enter command prefix/i)
fireEvent.change(input, { target: { value: "npm test" } })
const addButton = screen.getByText("Add")
fireEvent.click(addButton)
// Click Done
const doneButton = screen.getByText("Done")
fireEvent.click(doneButton)
// Verify VSCode messages were sent
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "allowedCommands",
commands: ["npm test"],
}),
)
expect(onDone).toHaveBeenCalled()
})
})