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) => (
Current config: {currentApiConfigName}
)
}))
// Mock VSCode components
jest.mock('@vscode/webview-ui-toolkit/react', () => ({
VSCodeButton: ({ children, onClick, appearance }: any) => (
appearance === 'icon' ?
:
),
VSCodeCheckbox: ({ children, onChange, checked }: any) => (
),
VSCodeTextField: ({ value, onInput, placeholder }: any) => (
onInput({ target: { value: e.target.value } })}
placeholder={placeholder}
/>
),
VSCodeTextArea: () => ,
VSCodeLink: ({ children, href }: any) => {children},
VSCodeDropdown: ({ children, value, onChange }: any) => (
),
VSCodeOption: ({ children, value }: any) => (
),
VSCodeRadio: ({ children, value, checked, onChange }: any) => (
),
VSCodeRadioGroup: ({ children, value, onChange }: any) => (
{children}
),
VSCodeSlider: ({ value, onChange }: any) => (
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(
)
// 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()
})
})