fix: address PR #4712 review feedback for diagnostics settings

- Added DiagnosticsSettings component to SettingsView with proper integration
- Added missing localization keys for diagnostics section
- Standardized includeDiagnostics default value to false across all files
- Fixed double configuration reading in diagnosticsToProblemsString
- Updated filter logic to use exact matching for diagnostic codes
- Replaced any types with proper React event types
- Added UI validation for max diagnostics count (0-200 range)
- Added comprehensive test coverage for new functionality
- Bumped version to 3.20.4
This commit is contained in:
hannesrudolph 2025-06-16 09:47:09 -06:00
parent 8117308183
commit a46783ffbe
10 changed files with 619 additions and 23 deletions

View file

@ -248,7 +248,7 @@ async function getFileOrFolderContent(
async function getWorkspaceProblems(cwd: string): Promise<string> {
// Check if diagnostics are enabled
const config = vscode.workspace.getConfiguration("roo-cline")
const includeDiagnostics = config.get<boolean>("includeDiagnostics", true)
const includeDiagnostics = config.get<boolean>("includeDiagnostics", false)
if (!includeDiagnostics) {
return "Diagnostics are disabled in settings."

View file

@ -1443,8 +1443,8 @@ export class ClineProvider
maxReadFileLine: maxReadFileLine ?? -1,
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
includeDiagnostics: includeDiagnostics ?? false,
maxDiagnosticsCount: maxDiagnosticsCount ?? 5,
diagnosticsFilter: diagnosticsFilter ?? ["error", "warning"],
maxDiagnosticsCount: maxDiagnosticsCount ?? 50,
diagnosticsFilter: diagnosticsFilter ?? [],
settingsImportedAt: this.settingsImportedAt,
terminalCompressProgressBar: terminalCompressProgressBar ?? true,
hasSystemPromptOverride,
@ -1597,8 +1597,8 @@ export class ClineProvider
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
includeDiagnostics: stateValues.includeDiagnostics ?? false,
maxDiagnosticsCount: stateValues.maxDiagnosticsCount ?? 5,
diagnosticsFilter: stateValues.diagnosticsFilter ?? ["error", "warning"],
maxDiagnosticsCount: stateValues.maxDiagnosticsCount ?? 50,
diagnosticsFilter: stateValues.diagnosticsFilter ?? [],
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
cloudUserInfo,
cloudIsAuthenticated,

View file

@ -0,0 +1,180 @@
import * as vscode from "vscode"
import { ClineProvider } from "../ClineProvider"
import { ContextProxy } from "../../config/ContextProxy"
// Mock vscode
jest.mock("vscode", () => ({
workspace: {
getConfiguration: jest.fn(() => ({
get: jest.fn(),
})),
},
ExtensionContext: jest.fn(),
OutputChannel: jest.fn(),
Uri: {
file: jest.fn((path: string) => ({ fsPath: path })),
},
ExtensionMode: {
Development: 1,
Production: 2,
Test: 3,
},
}))
// Mock other dependencies
jest.mock("../../config/ContextProxy")
jest.mock("../../../services/code-index/manager")
jest.mock("../../../services/mcp/McpServerManager")
jest.mock("../../config/CustomModesManager")
jest.mock("../../../services/marketplace/MarketplaceManager")
describe("Diagnostics Settings Integration", () => {
let provider: ClineProvider
let mockContext: any
let mockOutputChannel: any
let mockContextProxy: jest.Mocked<ContextProxy>
beforeEach(() => {
// Setup mocks
mockContext = {
globalState: {
get: jest.fn(),
update: jest.fn(),
keys: jest.fn(() => []),
},
secrets: {
get: jest.fn(),
store: jest.fn(),
},
globalStorageUri: { fsPath: "/test/storage" },
extensionUri: { fsPath: "/test/extension" },
extension: {
packageJSON: {
version: "1.0.0",
name: "test-extension",
},
},
}
mockOutputChannel = {
appendLine: jest.fn(),
}
mockContextProxy = {
getValue: jest.fn(),
setValue: jest.fn(),
getValues: jest.fn(() => ({
includeDiagnostics: false,
maxDiagnosticsCount: 50,
diagnosticsFilter: [],
})),
setValues: jest.fn(),
getProviderSettings: jest.fn(() => ({})),
setProviderSettings: jest.fn(),
resetAllState: jest.fn(),
extensionUri: { fsPath: "/test/extension" },
globalStorageUri: { fsPath: "/test/storage" },
extensionMode: vscode.ExtensionMode.Test,
} as any
// Create provider instance
provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", mockContextProxy)
})
afterEach(() => {
jest.clearAllMocks()
})
describe("Settings Persistence", () => {
it("should persist includeDiagnostics setting", async () => {
// Simulate setting update
await provider.setValue("includeDiagnostics", true)
expect(mockContextProxy.setValue).toHaveBeenCalledWith("includeDiagnostics", true)
})
it("should persist maxDiagnosticsCount setting", async () => {
// Simulate setting update
await provider.setValue("maxDiagnosticsCount", 100)
expect(mockContextProxy.setValue).toHaveBeenCalledWith("maxDiagnosticsCount", 100)
})
it("should persist diagnosticsFilter setting", async () => {
// Simulate setting update
const filters = ["eslint", "typescript"]
await provider.setValue("diagnosticsFilter", filters)
expect(mockContextProxy.setValue).toHaveBeenCalledWith("diagnosticsFilter", filters)
})
it("should retrieve diagnostics settings from state", () => {
// Setup mock return values
mockContextProxy.getValue.mockImplementation((key: string) => {
const values: Record<string, any> = {
includeDiagnostics: true,
maxDiagnosticsCount: 75,
diagnosticsFilter: ["error", "warning"],
}
return values[key]
})
// Retrieve settings
const includeDiagnostics = provider.getValue("includeDiagnostics")
const maxDiagnosticsCount = provider.getValue("maxDiagnosticsCount")
const diagnosticsFilter = provider.getValue("diagnosticsFilter")
expect(includeDiagnostics).toBe(true)
expect(maxDiagnosticsCount).toBe(75)
expect(diagnosticsFilter).toEqual(["error", "warning"])
})
it("should update multiple diagnostics settings at once", async () => {
const newSettings = {
includeDiagnostics: true,
maxDiagnosticsCount: 100,
diagnosticsFilter: ["eslint/no-unused-vars", "typescript"],
}
await provider.setValues(newSettings as any)
expect(mockContextProxy.setValues).toHaveBeenCalledWith(expect.objectContaining(newSettings))
})
})
describe("Default Values", () => {
it("should use correct default values when settings are undefined", () => {
mockContextProxy.getValue.mockReturnValue(undefined)
mockContextProxy.getValues.mockReturnValue({})
const state = provider.getValues()
// These defaults should match what's in the code
expect(state.includeDiagnostics ?? false).toBe(false)
expect(state.maxDiagnosticsCount ?? 50).toBe(50)
expect(state.diagnosticsFilter ?? []).toEqual([])
})
})
describe("Settings Validation", () => {
it("should validate maxDiagnosticsCount range", async () => {
// Test valid range
await provider.setValue("maxDiagnosticsCount", 100)
expect(mockContextProxy.setValue).toHaveBeenCalledWith("maxDiagnosticsCount", 100)
// Note: Validation should be done in the UI component
// The provider itself doesn't validate ranges
})
it("should handle empty diagnosticsFilter", async () => {
await provider.setValue("diagnosticsFilter", [])
expect(mockContextProxy.setValue).toHaveBeenCalledWith("diagnosticsFilter", [])
})
it("should handle diagnosticsFilter with multiple values", async () => {
const filters = ["eslint", "typescript", "dart Error", "custom-linter"]
await provider.setValue("diagnosticsFilter", filters)
expect(mockContextProxy.setValue).toHaveBeenCalledWith("diagnosticsFilter", filters)
})
})
})

View file

@ -0,0 +1,198 @@
import * as vscode from "vscode"
import { diagnosticsToProblemsString } from "../index"
// Mock vscode
jest.mock("vscode", () => ({
workspace: {
getConfiguration: jest.fn(() => ({
get: jest.fn((key: string, defaultValue: any) => {
const config: Record<string, any> = {
includeDiagnostics: false,
maxDiagnosticsCount: 50,
diagnosticsFilter: [],
}
return config[key] ?? defaultValue
}),
})),
fs: {
stat: jest.fn(),
},
openTextDocument: jest.fn(),
},
DiagnosticSeverity: {
Error: 0,
Warning: 1,
Information: 2,
Hint: 3,
},
FileType: {
File: 1,
Directory: 2,
},
Uri: {
file: (path: string) => ({ fsPath: path }),
},
Range: jest.fn((startLine: number, startChar: number, endLine: number, endChar: number) => ({
start: { line: startLine, character: startChar },
end: { line: endLine, character: endChar },
})),
Position: jest.fn((line: number, char: number) => ({ line, character: char })),
}))
describe("diagnosticsToProblemsString", () => {
const mockCwd = "/test/workspace"
beforeEach(() => {
jest.clearAllMocks()
})
it("should return empty string when includeDiagnostics is false", async () => {
const diagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
[
vscode.Uri.file("/test/workspace/file.ts"),
[
{
range: new vscode.Range(0, 0, 0, 10),
message: "Test error",
severity: vscode.DiagnosticSeverity.Error,
} as vscode.Diagnostic,
],
],
]
const result = await diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error], mockCwd, {
includeDiagnostics: false,
})
expect(result).toBe("")
})
it("should include diagnostics when includeDiagnostics is true", async () => {
const mockDocument = {
lineAt: jest.fn(() => ({ text: "const x = 1" })),
}
;(vscode.workspace.openTextDocument as jest.Mock).mockResolvedValue(mockDocument)
;(vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ type: vscode.FileType.File })
const diagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
[
vscode.Uri.file("/test/workspace/file.ts"),
[
{
range: new vscode.Range(0, 0, 0, 10),
message: "Test error",
severity: vscode.DiagnosticSeverity.Error,
source: "typescript",
} as vscode.Diagnostic,
],
],
]
const result = await diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error], mockCwd, {
includeDiagnostics: true,
})
expect(result).toContain("file.ts")
expect(result).toContain("Test error")
expect(result).toContain("typescript")
})
it("should respect maxDiagnosticsCount", async () => {
const mockDocument = {
lineAt: jest.fn(() => ({ text: "const x = 1" })),
}
;(vscode.workspace.openTextDocument as jest.Mock).mockResolvedValue(mockDocument)
;(vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ type: vscode.FileType.File })
const diagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
[
vscode.Uri.file("/test/workspace/file.ts"),
Array(10)
.fill(null)
.map(
(_, i) =>
({
range: new vscode.Range(i, 0, i, 10),
message: `Test error ${i}`,
severity: vscode.DiagnosticSeverity.Error,
}) as vscode.Diagnostic,
),
],
]
const result = await diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error], mockCwd, {
includeDiagnostics: true,
maxDiagnosticsCount: 3,
})
expect(result).toContain("Test error 0")
expect(result).toContain("Test error 1")
expect(result).toContain("Test error 2")
expect(result).not.toContain("Test error 3")
expect(result).toContain("7 more diagnostics omitted")
})
it("should apply diagnosticsFilter correctly", async () => {
const mockDocument = {
lineAt: jest.fn(() => ({ text: "const x = 1" })),
}
;(vscode.workspace.openTextDocument as jest.Mock).mockResolvedValue(mockDocument)
;(vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ type: vscode.FileType.File })
const diagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
[
vscode.Uri.file("/test/workspace/file.ts"),
[
{
range: new vscode.Range(0, 0, 0, 10),
message: "ESLint error",
severity: vscode.DiagnosticSeverity.Error,
source: "eslint",
code: "no-unused-vars",
} as vscode.Diagnostic,
{
range: new vscode.Range(1, 0, 1, 10),
message: "TypeScript error",
severity: vscode.DiagnosticSeverity.Error,
source: "typescript",
code: "2322",
} as vscode.Diagnostic,
],
],
]
const result = await diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error], mockCwd, {
includeDiagnostics: true,
diagnosticsFilter: ["typescript 2322"],
})
expect(result).toContain("TypeScript error")
expect(result).not.toContain("ESLint error")
})
it("should handle file read errors gracefully", async () => {
;(vscode.workspace.openTextDocument as jest.Mock).mockRejectedValue(new Error("File not found"))
;(vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({ type: vscode.FileType.File })
const diagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
[
vscode.Uri.file("/test/workspace/file.ts"),
[
{
range: new vscode.Range(0, 0, 0, 10),
message: "Test error",
severity: vscode.DiagnosticSeverity.Error,
} as vscode.Diagnostic,
],
],
]
const result = await diagnosticsToProblemsString(diagnostics, [vscode.DiagnosticSeverity.Error], mockCwd, {
includeDiagnostics: true,
})
expect(result).toContain("file.ts")
expect(result).toContain("(unavailable)")
expect(result).toContain("Test error")
})
})

View file

@ -81,20 +81,15 @@ export async function diagnosticsToProblemsString(
},
): Promise<string> {
// Use provided options or fall back to VSCode configuration
const includeDiagnostics =
options?.includeDiagnostics ??
vscode.workspace.getConfiguration("roo-cline").get<boolean>("includeDiagnostics", false)
const config = vscode.workspace.getConfiguration("roo-cline")
const includeDiagnostics = options?.includeDiagnostics ?? config.get<boolean>("includeDiagnostics", false)
if (!includeDiagnostics) {
return ""
}
const maxDiagnosticsCount =
options?.maxDiagnosticsCount ??
vscode.workspace.getConfiguration("roo-cline").get<number>("maxDiagnosticsCount", 5)
const diagnosticsFilter =
options?.diagnosticsFilter ??
vscode.workspace.getConfiguration("roo-cline").get<string[]>("diagnosticsFilter", ["error", "warning"])
const maxDiagnosticsCount = options?.maxDiagnosticsCount ?? config.get<number>("maxDiagnosticsCount", 50)
const diagnosticsFilter = options?.diagnosticsFilter ?? config.get<string[]>("diagnosticsFilter", [])
const documents = new Map<vscode.Uri, vscode.TextDocument>()
const fileStats = new Map<vscode.Uri, vscode.FileStat>()
@ -112,10 +107,10 @@ export async function diagnosticsToProblemsString(
const code = typeof d.code === "object" ? d.code.value : d.code
const filterKey = source ? `${source} ${code || ""}`.trim() : `${code || ""}`.trim()
// Check if this diagnostic should be filtered out
return !diagnosticsFilter.some((filter) => {
// Support partial matching
return filterKey.includes(filter) || d.message.includes(filter)
// Check if this diagnostic matches any filter (exact match)
return diagnosticsFilter.some((filter) => {
// Exact matching for filter key
return filterKey === filter || (filter && filterKey.startsWith(filter + " "))
})
})
.sort((a, b) => a.range.start.line - b.range.start.line)

View file

@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "RooVeterinaryInc",
"version": "3.20.3",
"version": "3.20.4",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
@ -347,7 +347,7 @@
},
"roo-cline.includeDiagnostics": {
"type": "boolean",
"default": true,
"default": false,
"description": "%settings.includeDiagnostics.description%"
},
"roo-cline.maxDiagnosticsCount": {

View file

@ -1,4 +1,4 @@
import { HTMLAttributes } from "react"
import { HTMLAttributes, ChangeEvent } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { AlertCircle } from "lucide-react"
@ -48,7 +48,9 @@ export const DiagnosticsSettings = ({
<div>
<VSCodeCheckbox
checked={includeDiagnostics}
onChange={(e: any) => setCachedStateField("includeDiagnostics", e.target.checked)}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setCachedStateField("includeDiagnostics", e.target.checked)
}
data-testid="include-diagnostics-checkbox">
<label className="block font-medium mb-1">
{t("settings:diagnostics.includeDiagnostics.label")}
@ -71,7 +73,11 @@ export const DiagnosticsSettings = ({
max={200}
step={1}
value={[maxDiagnosticsCount ?? 50]}
onValueChange={([value]) => setCachedStateField("maxDiagnosticsCount", value)}
onValueChange={([value]) => {
if (value >= 0 && value <= 200) {
setCachedStateField("maxDiagnosticsCount", value)
}
}}
data-testid="max-diagnostics-count-slider"
/>
<span className="w-10">{maxDiagnosticsCount ?? 50}</span>

View file

@ -64,6 +64,7 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
import { DiagnosticsSettings } from "./DiagnosticsSettings"
import { cn } from "@/lib/utils"
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
@ -84,6 +85,7 @@ const sectionNames = [
"checkpoints",
"notifications",
"contextManagement",
"diagnostics",
"terminal",
"prompts",
"experimental",
@ -172,6 +174,9 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
codebaseIndexConfig,
codebaseIndexModels,
customSupportPrompts,
includeDiagnostics,
maxDiagnosticsCount,
diagnosticsFilter,
} = cachedState
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@ -315,6 +320,9 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
vscode.postMessage({ type: "codebaseIndexConfig", values: codebaseIndexConfig })
vscode.postMessage({ type: "includeDiagnostics", bool: includeDiagnostics })
vscode.postMessage({ type: "maxDiagnosticsCount", value: maxDiagnosticsCount })
vscode.postMessage({ type: "diagnosticsFilter", values: diagnosticsFilter })
setChangeDetected(false)
}
}
@ -390,6 +398,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{ id: "checkpoints", icon: GitBranch },
{ id: "notifications", icon: Bell },
{ id: "contextManagement", icon: Database },
{ id: "diagnostics", icon: AlertTriangle },
{ id: "terminal", icon: SquareTerminal },
{ id: "prompts", icon: MessageSquare },
{ id: "experimental", icon: FlaskConical },
@ -648,6 +657,16 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
/>
)}
{/* Diagnostics Section */}
{activeTab === "diagnostics" && (
<DiagnosticsSettings
includeDiagnostics={includeDiagnostics}
maxDiagnosticsCount={maxDiagnosticsCount}
diagnosticsFilter={diagnosticsFilter}
setCachedStateField={setCachedStateField}
/>
)}
{/* Terminal Section */}
{activeTab === "terminal" && (
<TerminalSettings

View file

@ -0,0 +1,182 @@
import React from "react"
import { render, screen, fireEvent } from "@testing-library/react"
import "@testing-library/jest-dom"
import { DiagnosticsSettings } from "../DiagnosticsSettings"
// Mock the translation hook
jest.mock("@src/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
t: (key: string) => key,
}),
}))
// Mock VSCode components
jest.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeCheckbox: ({ children, onChange, checked, ...props }: any) => (
<label>
<input type="checkbox" checked={checked} onChange={onChange} {...props} />
{children}
</label>
),
}))
// Mock Slider component
jest.mock("@src/components/ui/slider", () => ({
Slider: ({ value, onValueChange, min, max, ...props }: any) => (
<input
type="range"
value={value?.[0] || 0}
onChange={(e) => onValueChange([parseInt(e.target.value)])}
min={min}
max={max}
{...props}
/>
),
}))
// Mock Input component
jest.mock("@src/components/ui/input", () => ({
Input: (props: any) => <input {...props} />,
}))
// Mock SectionHeader component
jest.mock("../SectionHeader", () => ({
SectionHeader: ({ children, description }: any) => (
<div>
<div>{children}</div>
{description && <div>{description}</div>}
</div>
),
}))
// Mock Section component
jest.mock("../Section", () => ({
Section: ({ children }: any) => <div>{children}</div>,
}))
describe("DiagnosticsSettings", () => {
const mockSetCachedStateField = jest.fn()
const defaultProps = {
includeDiagnostics: false,
maxDiagnosticsCount: 50,
diagnosticsFilter: ["error", "warning"],
setCachedStateField: mockSetCachedStateField,
}
beforeEach(() => {
jest.clearAllMocks()
})
it("renders all diagnostic settings", () => {
render(<DiagnosticsSettings {...defaultProps} />)
expect(screen.getByText("settings:sections.diagnostics")).toBeInTheDocument()
expect(screen.getByText("settings:diagnostics.description")).toBeInTheDocument()
expect(screen.getByTestId("include-diagnostics-checkbox")).toBeInTheDocument()
expect(screen.getByTestId("max-diagnostics-count-slider")).toBeInTheDocument()
expect(screen.getByTestId("diagnostics-filter-input")).toBeInTheDocument()
})
it("displays current values correctly", () => {
render(<DiagnosticsSettings {...defaultProps} includeDiagnostics={true} />)
const checkbox = screen.getByTestId("include-diagnostics-checkbox") as HTMLInputElement
expect(checkbox.checked).toBe(true)
const slider = screen.getByTestId("max-diagnostics-count-slider") as HTMLInputElement
expect(slider.value).toBe("50")
const filterInput = screen.getByTestId("diagnostics-filter-input") as HTMLInputElement
expect(filterInput.value).toBe("error, warning")
})
it("calls setCachedStateField when checkbox is toggled", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const checkbox = screen.getByTestId("include-diagnostics-checkbox")
fireEvent.change(checkbox, { target: { checked: true } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("includeDiagnostics", true)
})
it("calls setCachedStateField when slider value changes", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const slider = screen.getByTestId("max-diagnostics-count-slider")
fireEvent.change(slider, { target: { value: "75" } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("maxDiagnosticsCount", 75)
})
it("validates slider value range", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const slider = screen.getByTestId("max-diagnostics-count-slider")
// Test value above max
fireEvent.change(slider, { target: { value: "250" } })
expect(mockSetCachedStateField).not.toHaveBeenCalledWith("maxDiagnosticsCount", 250)
// Test negative value
fireEvent.change(slider, { target: { value: "-10" } })
expect(mockSetCachedStateField).not.toHaveBeenCalledWith("maxDiagnosticsCount", -10)
// Test valid value
fireEvent.change(slider, { target: { value: "100" } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("maxDiagnosticsCount", 100)
})
it("calls setCachedStateField when filter input changes", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const filterInput = screen.getByTestId("diagnostics-filter-input")
fireEvent.change(filterInput, { target: { value: "eslint, typescript" } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("diagnosticsFilter", ["eslint", "typescript"])
})
it("handles empty filter input", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const filterInput = screen.getByTestId("diagnostics-filter-input")
fireEvent.change(filterInput, { target: { value: "" } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("diagnosticsFilter", [])
})
it("trims whitespace from filter values", () => {
render(<DiagnosticsSettings {...defaultProps} />)
const filterInput = screen.getByTestId("diagnostics-filter-input")
fireEvent.change(filterInput, { target: { value: " eslint , typescript " } })
expect(mockSetCachedStateField).toHaveBeenCalledWith("diagnosticsFilter", ["eslint", "typescript"])
})
it("renders with undefined props", () => {
render(<DiagnosticsSettings setCachedStateField={mockSetCachedStateField} />)
const checkbox = screen.getByTestId("include-diagnostics-checkbox") as HTMLInputElement
expect(checkbox.checked).toBe(false)
const slider = screen.getByTestId("max-diagnostics-count-slider") as HTMLInputElement
expect(slider.value).toBe("50")
const filterInput = screen.getByTestId("diagnostics-filter-input") as HTMLInputElement
expect(filterInput.value).toBe("")
})
it("displays correct count value next to slider", () => {
render(<DiagnosticsSettings {...defaultProps} maxDiagnosticsCount={75} />)
expect(screen.getByText("75")).toBeInTheDocument()
})
it("applies custom className", () => {
const { container } = render(<DiagnosticsSettings {...defaultProps} className="custom-class" />)
const rootElement = container.firstChild as HTMLElement
expect(rootElement).toHaveClass("custom-class")
})
})

View file

@ -27,6 +27,7 @@
"checkpoints": "Checkpoints",
"notifications": "Notifications",
"contextManagement": "Context",
"diagnostics": "Diagnostics",
"terminal": "Terminal",
"prompts": "Prompts",
"experimental": "Experimental",
@ -395,6 +396,21 @@
"always_full_read": "Always read entire file"
}
},
"diagnostics": {
"description": "Configure how workspace diagnostics (errors and warnings) are included in the AI's context",
"includeDiagnostics": {
"label": "Include diagnostics in context",
"description": "When enabled, workspace errors and warnings will be included when using @problems mention"
},
"maxDiagnosticsCount": {
"label": "Maximum diagnostics count",
"description": "Limit the number of diagnostics included to prevent excessive token usage"
},
"diagnosticsFilter": {
"label": "Diagnostics filter",
"description": "Filter diagnostics by source and code (e.g., 'eslint', 'typescript', 'dart Error'). Leave empty to include all diagnostics"
}
},
"terminal": {
"basic": {
"label": "Terminal Settings: Basic",