mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
ux: add downloadable error diagnostics from chat errors (#10188)
Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com> Co-authored-by: Chris Estreich <cestreich@gmail.com>
This commit is contained in:
parent
6e2b85214e
commit
bb358fb8f9
25 changed files with 525 additions and 30 deletions
188
src/core/webview/__tests__/diagnosticsHandler.spec.ts
Normal file
188
src/core/webview/__tests__/diagnosticsHandler.spec.ts
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// npx vitest src/core/webview/__tests__/diagnosticsHandler.spec.ts
|
||||
|
||||
import * as path from "path"
|
||||
|
||||
// Mock vscode first
|
||||
vi.mock("vscode", () => {
|
||||
const showErrorMessage = vi.fn()
|
||||
const openTextDocument = vi.fn().mockResolvedValue({})
|
||||
const showTextDocument = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
return {
|
||||
window: {
|
||||
showErrorMessage,
|
||||
showTextDocument,
|
||||
},
|
||||
workspace: {
|
||||
openTextDocument,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Mock storage utilities
|
||||
vi.mock("../../../utils/storage", () => ({
|
||||
getTaskDirectoryPath: vi.fn(async () => "/mock/task-dir"),
|
||||
}))
|
||||
|
||||
// Mock fs utilities
|
||||
vi.mock("../../../utils/fs", () => ({
|
||||
fileExistsAtPath: vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock fs/promises
|
||||
vi.mock("fs/promises", () => {
|
||||
const mockReadFile = vi.fn()
|
||||
const mockWriteFile = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
return {
|
||||
default: {
|
||||
readFile: mockReadFile,
|
||||
writeFile: mockWriteFile,
|
||||
},
|
||||
readFile: mockReadFile,
|
||||
writeFile: mockWriteFile,
|
||||
}
|
||||
})
|
||||
|
||||
import * as vscode from "vscode"
|
||||
import * as fs from "fs/promises"
|
||||
import * as fsUtils from "../../../utils/fs"
|
||||
import { generateErrorDiagnostics } from "../diagnosticsHandler"
|
||||
|
||||
describe("generateErrorDiagnostics", () => {
|
||||
const mockLog = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("generates a diagnostics file with error metadata and history", async () => {
|
||||
vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(true as any)
|
||||
vi.mocked(fs.readFile).mockResolvedValue('[{"role": "user", "content": "test"}]' as any)
|
||||
|
||||
const result = await generateErrorDiagnostics({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
values: {
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
version: "1.2.3",
|
||||
provider: "test-provider",
|
||||
model: "test-model",
|
||||
details: "Sample error details",
|
||||
},
|
||||
log: mockLog,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.filePath).toContain("roo-diagnostics-")
|
||||
|
||||
// Verify we attempted to read API history
|
||||
expect(fs.readFile).toHaveBeenCalledWith(path.join("/mock/task-dir", "api_conversation_history.json"), "utf8")
|
||||
|
||||
// Verify we wrote a diagnostics file with the expected content
|
||||
expect(fs.writeFile).toHaveBeenCalledTimes(1)
|
||||
const [writtenPath, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
|
||||
// taskId.slice(0, 8) = "test-tas" from "test-task-id"
|
||||
expect(String(writtenPath)).toContain("roo-diagnostics-test-tas")
|
||||
expect(String(writtenContent)).toContain(
|
||||
"// Please share this file with Roo Code Support (support@roocode.com) to diagnose the issue faster",
|
||||
)
|
||||
expect(String(writtenContent)).toContain('"error":')
|
||||
expect(String(writtenContent)).toContain('"history":')
|
||||
expect(String(writtenContent)).toContain('"version": "1.2.3"')
|
||||
expect(String(writtenContent)).toContain('"provider": "test-provider"')
|
||||
expect(String(writtenContent)).toContain('"model": "test-model"')
|
||||
expect(String(writtenContent)).toContain('"details": "Sample error details"')
|
||||
|
||||
// Verify VS Code APIs were used to open the generated file
|
||||
expect(vscode.workspace.openTextDocument).toHaveBeenCalledTimes(1)
|
||||
expect(vscode.window.showTextDocument).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("uses empty history when API history file does not exist", async () => {
|
||||
vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
|
||||
|
||||
const result = await generateErrorDiagnostics({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
values: {
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
version: "1.0.0",
|
||||
provider: "test",
|
||||
model: "test",
|
||||
details: "error",
|
||||
},
|
||||
log: mockLog,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Should not attempt to read file when it doesn't exist
|
||||
expect(fs.readFile).not.toHaveBeenCalled()
|
||||
|
||||
// Verify empty history in output
|
||||
const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
|
||||
expect(String(writtenContent)).toContain('"history": []')
|
||||
})
|
||||
|
||||
it("uses default values when values are not provided", async () => {
|
||||
vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
|
||||
|
||||
const result = await generateErrorDiagnostics({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
log: mockLog,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Verify defaults in output
|
||||
const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
|
||||
expect(String(writtenContent)).toContain('"version": ""')
|
||||
expect(String(writtenContent)).toContain('"provider": ""')
|
||||
expect(String(writtenContent)).toContain('"model": ""')
|
||||
expect(String(writtenContent)).toContain('"details": ""')
|
||||
})
|
||||
|
||||
it("handles JSON parse error gracefully", async () => {
|
||||
vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(true as any)
|
||||
vi.mocked(fs.readFile).mockResolvedValue("invalid json" as any)
|
||||
|
||||
const result = await generateErrorDiagnostics({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
values: {
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
version: "1.0.0",
|
||||
provider: "test",
|
||||
model: "test",
|
||||
details: "error",
|
||||
},
|
||||
log: mockLog,
|
||||
})
|
||||
|
||||
// Should still succeed but with empty history
|
||||
expect(result.success).toBe(true)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to parse api_conversation_history.json")
|
||||
|
||||
// Verify empty history in output
|
||||
const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
|
||||
expect(String(writtenContent)).toContain('"history": []')
|
||||
})
|
||||
|
||||
it("returns error result when file write fails", async () => {
|
||||
vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
|
||||
vi.mocked(fs.writeFile).mockRejectedValue(new Error("Write failed"))
|
||||
|
||||
const result = await generateErrorDiagnostics({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
log: mockLog,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toBe("Write failed")
|
||||
expect(mockLog).toHaveBeenCalledWith("Error generating diagnostics: Write failed")
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to generate diagnostics: Write failed")
|
||||
})
|
||||
})
|
||||
|
|
@ -5,6 +5,11 @@ import type { Mock } from "vitest"
|
|||
// Mock dependencies - must come before imports
|
||||
vi.mock("../../../api/providers/fetchers/modelCache")
|
||||
|
||||
// Mock the diagnosticsHandler module
|
||||
vi.mock("../diagnosticsHandler", () => ({
|
||||
generateErrorDiagnostics: vi.fn().mockResolvedValue({ success: true, filePath: "/tmp/diagnostics.json" }),
|
||||
}))
|
||||
|
||||
import { webviewMessageHandler } from "../webviewMessageHandler"
|
||||
import type { ClineProvider } from "../ClineProvider"
|
||||
import { getModels } from "../../../api/providers/fetchers/modelCache"
|
||||
|
|
@ -41,15 +46,24 @@ const mockClineProvider = {
|
|||
|
||||
import { t } from "../../../i18n"
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
window: {
|
||||
showInformationMessage: vi.fn(),
|
||||
showErrorMessage: vi.fn(),
|
||||
},
|
||||
workspace: {
|
||||
workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }],
|
||||
},
|
||||
}))
|
||||
vi.mock("vscode", () => {
|
||||
const showInformationMessage = vi.fn()
|
||||
const showErrorMessage = vi.fn()
|
||||
const openTextDocument = vi.fn().mockResolvedValue({})
|
||||
const showTextDocument = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
return {
|
||||
window: {
|
||||
showInformationMessage,
|
||||
showErrorMessage,
|
||||
showTextDocument,
|
||||
},
|
||||
workspace: {
|
||||
workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }],
|
||||
openTextDocument,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("../../../i18n", () => ({
|
||||
t: vi.fn((key: string, args?: Record<string, any>) => {
|
||||
|
|
@ -72,14 +86,20 @@ vi.mock("../../../i18n", () => ({
|
|||
vi.mock("fs/promises", () => {
|
||||
const mockRm = vi.fn().mockResolvedValue(undefined)
|
||||
const mockMkdir = vi.fn().mockResolvedValue(undefined)
|
||||
const mockReadFile = vi.fn().mockResolvedValue("[]")
|
||||
const mockWriteFile = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
return {
|
||||
default: {
|
||||
rm: mockRm,
|
||||
mkdir: mockMkdir,
|
||||
readFile: mockReadFile,
|
||||
writeFile: mockWriteFile,
|
||||
},
|
||||
rm: mockRm,
|
||||
mkdir: mockMkdir,
|
||||
readFile: mockReadFile,
|
||||
writeFile: mockWriteFile,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -90,6 +110,7 @@ import * as path from "path"
|
|||
import * as fsUtils from "../../../utils/fs"
|
||||
import { getWorkspacePath } from "../../../utils/path"
|
||||
import { ensureSettingsDirectoryExists } from "../../../utils/globalContext"
|
||||
import { generateErrorDiagnostics } from "../diagnosticsHandler"
|
||||
import type { ModeConfig } from "@roo-code/types"
|
||||
|
||||
vi.mock("../../../utils/fs")
|
||||
|
|
@ -739,3 +760,57 @@ describe("webviewMessageHandler - mcpEnabled", () => {
|
|||
expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("webviewMessageHandler - downloadErrorDiagnostics", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Ensure contextProxy has a globalStorageUri for the handler
|
||||
;(mockClineProvider as any).contextProxy.globalStorageUri = { fsPath: "/mock/global/storage" }
|
||||
|
||||
// Provide a current task with a stable ID
|
||||
vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({
|
||||
taskId: "test-task-id",
|
||||
} as any)
|
||||
})
|
||||
|
||||
it("calls generateErrorDiagnostics with correct parameters", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "downloadErrorDiagnostics",
|
||||
values: {
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
version: "1.2.3",
|
||||
provider: "test-provider",
|
||||
model: "test-model",
|
||||
details: "Sample error details",
|
||||
},
|
||||
} as any)
|
||||
|
||||
// Verify generateErrorDiagnostics was called with the correct parameters
|
||||
expect(generateErrorDiagnostics).toHaveBeenCalledTimes(1)
|
||||
expect(generateErrorDiagnostics).toHaveBeenCalledWith({
|
||||
taskId: "test-task-id",
|
||||
globalStoragePath: "/mock/global/storage",
|
||||
values: {
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
version: "1.2.3",
|
||||
provider: "test-provider",
|
||||
model: "test-model",
|
||||
details: "Sample error details",
|
||||
},
|
||||
log: expect.any(Function),
|
||||
})
|
||||
})
|
||||
|
||||
it("shows error when no active task", async () => {
|
||||
vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue(null as any)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "downloadErrorDiagnostics",
|
||||
values: {},
|
||||
} as any)
|
||||
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("No active task to generate diagnostics for")
|
||||
expect(generateErrorDiagnostics).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
92
src/core/webview/diagnosticsHandler.ts
Normal file
92
src/core/webview/diagnosticsHandler.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import * as fs from "fs/promises"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import { getTaskDirectoryPath } from "../../utils/storage"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
|
||||
export interface ErrorDiagnosticsValues {
|
||||
timestamp?: string
|
||||
version?: string
|
||||
provider?: string
|
||||
model?: string
|
||||
details?: string
|
||||
}
|
||||
|
||||
export interface GenerateDiagnosticsParams {
|
||||
taskId: string
|
||||
globalStoragePath: string
|
||||
values?: ErrorDiagnosticsValues
|
||||
log: (message: string) => void
|
||||
}
|
||||
|
||||
export interface GenerateDiagnosticsResult {
|
||||
success: boolean
|
||||
filePath?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an error diagnostics file containing error metadata and API conversation history.
|
||||
* The file is created in the system temp directory and opened in VS Code for the user to review
|
||||
* before sharing with support.
|
||||
*/
|
||||
export async function generateErrorDiagnostics(params: GenerateDiagnosticsParams): Promise<GenerateDiagnosticsResult> {
|
||||
const { taskId, globalStoragePath, values, log } = params
|
||||
|
||||
try {
|
||||
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, taskId)
|
||||
|
||||
// Load API conversation history from the same file used by openDebugApiHistory
|
||||
const apiHistoryPath = path.join(taskDirPath, "api_conversation_history.json")
|
||||
let history: unknown = []
|
||||
|
||||
if (await fileExistsAtPath(apiHistoryPath)) {
|
||||
const content = await fs.readFile(apiHistoryPath, "utf8")
|
||||
try {
|
||||
history = JSON.parse(content)
|
||||
} catch {
|
||||
// If parsing fails, fall back to empty history but still generate diagnostics file
|
||||
vscode.window.showErrorMessage("Failed to parse api_conversation_history.json")
|
||||
}
|
||||
}
|
||||
|
||||
const diagnostics = {
|
||||
error: {
|
||||
timestamp: values?.timestamp ?? new Date().toISOString(),
|
||||
version: values?.version ?? "",
|
||||
provider: values?.provider ?? "",
|
||||
model: values?.model ?? "",
|
||||
details: values?.details ?? "",
|
||||
},
|
||||
history,
|
||||
}
|
||||
|
||||
// Prepend human-readable guidance comments before the JSON payload
|
||||
const headerComment =
|
||||
"// Please share this file with Roo Code Support (support@roocode.com) to diagnose the issue faster\n" +
|
||||
"// Just make sure you're OK sharing the contents of the conversation below.\n\n"
|
||||
const jsonContent = JSON.stringify(diagnostics, null, 2)
|
||||
const fullContent = headerComment + jsonContent
|
||||
|
||||
// Create a temporary diagnostics file
|
||||
const tmpDir = os.tmpdir()
|
||||
const timestamp = Date.now()
|
||||
const tempFileName = `roo-diagnostics-${taskId.slice(0, 8)}-${timestamp}.json`
|
||||
const tempFilePath = path.join(tmpDir, tempFileName)
|
||||
|
||||
await fs.writeFile(tempFilePath, fullContent, "utf8")
|
||||
|
||||
// Open the diagnostics file in VS Code
|
||||
const doc = await vscode.workspace.openTextDocument(tempFilePath)
|
||||
await vscode.window.showTextDocument(doc, { preview: true })
|
||||
|
||||
return { success: true, filePath: tempFilePath }
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
log(`Error generating diagnostics: ${errorMessage}`)
|
||||
vscode.window.showErrorMessage(`Failed to generate diagnostics: ${errorMessage}`)
|
||||
return { success: false, error: errorMessage }
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import { saveTaskMessages } from "../task-persistence"
|
|||
import { ClineProvider } from "./ClineProvider"
|
||||
import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager"
|
||||
import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler"
|
||||
import { generateErrorDiagnostics } from "./diagnosticsHandler"
|
||||
import { changeLanguage, t } from "../../i18n"
|
||||
import { Package } from "../../shared/package"
|
||||
import { type RouterName, type ModelRecord, toRouterName } from "../../shared/api"
|
||||
|
|
@ -3173,6 +3174,22 @@ export const webviewMessageHandler = async (
|
|||
break
|
||||
}
|
||||
|
||||
case "downloadErrorDiagnostics": {
|
||||
const currentTask = provider.getCurrentTask()
|
||||
if (!currentTask) {
|
||||
vscode.window.showErrorMessage("No active task to generate diagnostics for")
|
||||
break
|
||||
}
|
||||
|
||||
await generateErrorDiagnostics({
|
||||
taskId: currentTask.taskId,
|
||||
globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
|
||||
values: message.values,
|
||||
log: (msg) => provider.log(msg),
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
// console.log(`Unhandled message type: ${message.type}`)
|
||||
//
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ export interface WebviewMessage {
|
|||
| "browserPanelDidLaunch"
|
||||
| "openDebugApiHistory"
|
||||
| "openDebugUiHistory"
|
||||
| "downloadErrorDiagnostics"
|
||||
| "requestClaudeCodeRateLimits"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useState, useCallback, memo, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { BookOpenText, MessageCircleWarning, Copy, Check } from "lucide-react"
|
||||
import { BookOpenText, MessageCircleWarning, Copy, Check, Microscope } from "lucide-react"
|
||||
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import CodeBlock from "../common/CodeBlock"
|
||||
|
|
@ -112,6 +113,23 @@ export const ErrorRow = memo(
|
|||
return metadata + errorDetails
|
||||
}, [errorDetails, version, provider, modelId])
|
||||
|
||||
const handleDownloadDiagnostics = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({
|
||||
type: "downloadErrorDiagnostics",
|
||||
values: {
|
||||
timestamp: new Date().toISOString(),
|
||||
version,
|
||||
provider,
|
||||
model: modelId,
|
||||
details: errorDetails || "",
|
||||
},
|
||||
})
|
||||
},
|
||||
[version, provider, modelId, errorDetails],
|
||||
)
|
||||
|
||||
// Default titles for different error types
|
||||
const getDefaultTitle = () => {
|
||||
if (title) return title
|
||||
|
|
@ -278,7 +296,7 @@ export const ErrorRow = memo(
|
|||
</pre>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={handleCopyDetails}>
|
||||
<Button variant="secondary" className="w-full" onClick={handleCopyDetails}>
|
||||
{showDetailsCopySuccess ? (
|
||||
<>
|
||||
<Check className="size-3" />
|
||||
|
|
@ -291,6 +309,10 @@ export const ErrorRow = memo(
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="secondary" className="w-full" onClick={handleDownloadDiagnostics}>
|
||||
<Microscope className="size-3" />
|
||||
{t("chat:errorDetails.diagnostics")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
82
webview-ui/src/components/chat/__tests__/ErrorRow.spec.tsx
Normal file
82
webview-ui/src/components/chat/__tests__/ErrorRow.spec.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import React from "react"
|
||||
|
||||
import { render, screen, fireEvent } from "@/utils/test-utils"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
|
||||
import { ErrorRow } from "../ErrorRow"
|
||||
|
||||
// Mock vscode webview messaging
|
||||
vi.mock("@/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock ExtensionState context
|
||||
vi.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
version: "1.0.0",
|
||||
apiConfiguration: {},
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock selected model hook
|
||||
vi.mock("@/components/ui/hooks/useSelectedModel", () => ({
|
||||
useSelectedModel: () => ({
|
||||
provider: "test-provider",
|
||||
id: "test-model",
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock i18n
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const map: Record<string, string> = {
|
||||
"chat:error": "Error",
|
||||
"chat:errorDetails.title": "Error Details",
|
||||
"chat:errorDetails.copyToClipboard": "Copy to Clipboard",
|
||||
"chat:errorDetails.copied": "Copied!",
|
||||
"chat:errorDetails.diagnostics": "Get detailed error info",
|
||||
}
|
||||
return map[key] ?? key
|
||||
},
|
||||
}),
|
||||
initReactI18next: {
|
||||
type: "3rdParty",
|
||||
init: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe("ErrorRow diagnostics download", () => {
|
||||
it("sends downloadErrorDiagnostics message with error metadata", () => {
|
||||
const mockPostMessage = vi.mocked(vscode.postMessage)
|
||||
|
||||
render(<ErrorRow type="error" message="Something went wrong" errorDetails="Detailed error body" />)
|
||||
|
||||
// Open the Error Details dialog via the info button
|
||||
const infoButton = screen.getByRole("button", { name: "Error Details" })
|
||||
fireEvent.click(infoButton)
|
||||
|
||||
// Click the diagnostics button
|
||||
const downloadButton = screen.getByRole("button", { name: "Get detailed error info" })
|
||||
fireEvent.click(downloadButton)
|
||||
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
const call = mockPostMessage.mock.calls.find(([arg]) => arg.type === "downloadErrorDiagnostics")
|
||||
expect(call).toBeTruthy()
|
||||
if (!call) return
|
||||
|
||||
const payload = call[0] as { type: string; values?: any }
|
||||
expect(payload.values).toBeTruthy()
|
||||
if (!payload.values) return
|
||||
|
||||
expect(payload.values).toMatchObject({
|
||||
version: "1.0.0",
|
||||
provider: "test-provider",
|
||||
model: "test-model",
|
||||
})
|
||||
// Timestamp is generated at runtime, but should be a string
|
||||
expect(typeof payload.values.timestamp).toBe("string")
|
||||
})
|
||||
})
|
||||
3
webview-ui/src/i18n/locales/ca/chat.json
generated
3
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Details",
|
||||
"title": "Detalls de l'error",
|
||||
"copyToClipboard": "Copiar al porta-retalls",
|
||||
"copied": "Copiat!"
|
||||
"copied": "Copiat!",
|
||||
"diagnostics": "Obtenir informació d'error detallada"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Sembla que estàs tenint problemes amb Windows PowerShell, si us plau consulta aquesta documentació per a més informació."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/de/chat.json
generated
3
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Details",
|
||||
"title": "Fehlerdetails",
|
||||
"copyToClipboard": "In Zwischenablage kopieren",
|
||||
"copied": "Kopiert!"
|
||||
"copied": "Kopiert!",
|
||||
"diagnostics": "Detaillierte Fehlerinformationen abrufen"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Es scheint, dass du Probleme mit Windows PowerShell hast, bitte sieh dir dies an"
|
||||
|
|
|
|||
|
|
@ -293,8 +293,9 @@
|
|||
"errorDetails": {
|
||||
"link": "Details",
|
||||
"title": "Error Details",
|
||||
"copyToClipboard": "Copy to Clipboard",
|
||||
"copied": "Copied!"
|
||||
"copyToClipboard": "Copy basic error info",
|
||||
"copied": "Copied!",
|
||||
"diagnostics": "Get detailed error info"
|
||||
},
|
||||
"diffError": {
|
||||
"title": "Edit Unsuccessful"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/es/chat.json
generated
3
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Detalles",
|
||||
"title": "Detalles del error",
|
||||
"copyToClipboard": "Copiar al portapapeles",
|
||||
"copied": "¡Copiado!"
|
||||
"copied": "¡Copiado!",
|
||||
"diagnostics": "Obtener información de error detallada"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Parece que estás teniendo problemas con Windows PowerShell, por favor consulta esta"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/fr/chat.json
generated
3
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Détails",
|
||||
"title": "Détails de l'erreur",
|
||||
"copyToClipboard": "Copier dans le presse-papiers",
|
||||
"copied": "Copié !"
|
||||
"copied": "Copié !",
|
||||
"diagnostics": "Obtenir les détails de l'erreur"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Il semble que vous rencontriez des problèmes avec Windows PowerShell, veuillez consulter ce"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/hi/chat.json
generated
3
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "विवरण",
|
||||
"title": "त्रुटि विवरण",
|
||||
"copyToClipboard": "क्लिपबोर्ड पर कॉपी करें",
|
||||
"copied": "कॉपी किया गया!"
|
||||
"copied": "कॉपी किया गया!",
|
||||
"diagnostics": "विस्तृत त्रुटि जानकारी प्राप्त करें"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "ऐसा लगता है कि आपको Windows PowerShell के साथ समस्याएँ हो रही हैं, कृपया इसे देखें"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/id/chat.json
generated
3
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -303,7 +303,8 @@
|
|||
"link": "Details",
|
||||
"title": "Detail Kesalahan",
|
||||
"copyToClipboard": "Salin ke Clipboard",
|
||||
"copied": "Disalin!"
|
||||
"copied": "Disalin!",
|
||||
"diagnostics": "Dapatkan informasi kesalahan terperinci"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Sepertinya kamu mengalami masalah Windows PowerShell, silakan lihat ini"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/it/chat.json
generated
3
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Dettagli",
|
||||
"title": "Dettagli errore",
|
||||
"copyToClipboard": "Copia negli appunti",
|
||||
"copied": "Copiato!"
|
||||
"copied": "Copiato!",
|
||||
"diagnostics": "Ottieni informazioni errore dettagliate"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Sembra che tu stia avendo problemi con Windows PowerShell, consulta questa"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ja/chat.json
generated
3
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "詳細",
|
||||
"title": "エラー詳細",
|
||||
"copyToClipboard": "クリップボードにコピー",
|
||||
"copied": "コピーしました!"
|
||||
"copied": "コピーしました!",
|
||||
"diagnostics": "詳細なエラー情報を取得"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Windows PowerShellに問題があるようです。こちらを参照してください"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ko/chat.json
generated
3
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "자세히",
|
||||
"title": "오류 세부 정보",
|
||||
"copyToClipboard": "클립보드에 복사",
|
||||
"copied": "복사됨!"
|
||||
"copied": "복사됨!",
|
||||
"diagnostics": "상세한 오류 정보 가져오기"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Windows PowerShell에 문제가 있는 것 같습니다. 다음을 참조하세요"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/nl/chat.json
generated
3
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Details",
|
||||
"title": "Foutdetails",
|
||||
"copyToClipboard": "Naar klembord kopiëren",
|
||||
"copied": "Gekopieerd!"
|
||||
"copied": "Gekopieerd!",
|
||||
"diagnostics": "Gedetailleerde foutinformatie ophalen"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Het lijkt erop dat je problemen hebt met Windows PowerShell, zie deze"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pl/chat.json
generated
3
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Details",
|
||||
"title": "Szczegóły błędu",
|
||||
"copyToClipboard": "Kopiuj do schowka",
|
||||
"copied": "Skopiowano!"
|
||||
"copied": "Skopiowano!",
|
||||
"diagnostics": "Uzyskaj szczegółowe informacje o błędzie"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Wygląda na to, że masz problemy z Windows PowerShell, proszę zapoznaj się z tym"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
3
webview-ui/src/i18n/locales/pt-BR/chat.json
generated
|
|
@ -267,7 +267,8 @@
|
|||
"link": "Details",
|
||||
"title": "Detalhes do erro",
|
||||
"copyToClipboard": "Copiar para área de transferência",
|
||||
"copied": "Copiado!"
|
||||
"copied": "Copiado!",
|
||||
"diagnostics": "Obter informações detalhadas do erro"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Parece que você está tendo problemas com o Windows PowerShell, por favor veja este"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ru/chat.json
generated
3
webview-ui/src/i18n/locales/ru/chat.json
generated
|
|
@ -268,7 +268,8 @@
|
|||
"link": "Подробности",
|
||||
"title": "Детали ошибки",
|
||||
"copyToClipboard": "Скопировать в буфер обмена",
|
||||
"copied": "Скопировано!"
|
||||
"copied": "Скопировано!",
|
||||
"diagnostics": "Получить подробную информацию об ошибке"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Похоже, у вас проблемы с Windows PowerShell, пожалуйста, ознакомьтесь с этим"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/tr/chat.json
generated
3
webview-ui/src/i18n/locales/tr/chat.json
generated
|
|
@ -268,7 +268,8 @@
|
|||
"link": "Details",
|
||||
"title": "Hata Detayları",
|
||||
"copyToClipboard": "Panoya Kopyala",
|
||||
"copied": "Kopyalandı!"
|
||||
"copied": "Kopyalandı!",
|
||||
"diagnostics": "Ayrıntılı hata bilgisi al"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Windows PowerShell ile ilgili sorunlar yaşıyor gibi görünüyorsunuz, lütfen şu konuya bakın"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/vi/chat.json
generated
3
webview-ui/src/i18n/locales/vi/chat.json
generated
|
|
@ -268,7 +268,8 @@
|
|||
"link": "Chi tiết",
|
||||
"title": "Chi tiết lỗi",
|
||||
"copyToClipboard": "Sao chép vào clipboard",
|
||||
"copied": "Đã sao chép!"
|
||||
"copied": "Đã sao chép!",
|
||||
"diagnostics": "Nhận thông tin lỗi chi tiết"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "Có vẻ như bạn đang gặp vấn đề với Windows PowerShell, vui lòng xem"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
3
webview-ui/src/i18n/locales/zh-CN/chat.json
generated
|
|
@ -268,7 +268,8 @@
|
|||
"link": "详情",
|
||||
"title": "错误详情",
|
||||
"copyToClipboard": "复制到剪贴板",
|
||||
"copied": "已复制!"
|
||||
"copied": "已复制!",
|
||||
"diagnostics": "获取详细错误信息"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "看起来您遇到了Windows PowerShell问题,请参阅此"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
3
webview-ui/src/i18n/locales/zh-TW/chat.json
generated
|
|
@ -301,7 +301,8 @@
|
|||
"link": "詳情",
|
||||
"title": "錯誤詳細資訊",
|
||||
"copyToClipboard": "複製到剪貼簿",
|
||||
"copied": "已複製!"
|
||||
"copied": "已複製!",
|
||||
"diagnostics": "取得詳細錯誤資訊"
|
||||
},
|
||||
"powershell": {
|
||||
"issues": "您似乎遇到了 Windows PowerShell 的問題,請參閱此說明文件"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue