mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
8117308183
commit
a46783ffbe
10 changed files with 619 additions and 23 deletions
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue