mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Rough first version of bug reporting
This commit is contained in:
parent
95ba760daf
commit
37307d56a1
8 changed files with 623 additions and 2 deletions
|
|
@ -2020,6 +2020,24 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
|
|||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
|
||||
case "getBugReportInfo": {
|
||||
// Send environment information back to the webview for bug reporting
|
||||
const properties = await this.getTelemetryProperties()
|
||||
await this.postMessageToWebview({
|
||||
type: "bugReportInfo",
|
||||
info: properties,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "openExternal": {
|
||||
// Open an external URL (used for GitHub issue creation)
|
||||
if (message.url) {
|
||||
vscode.env.openExternal(vscode.Uri.parse(message.url))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export interface ExtensionMessage {
|
|||
| "ttsStop"
|
||||
| "maxReadFileLine"
|
||||
| "fileSearchResults"
|
||||
| "bugReportInfo"
|
||||
text?: string
|
||||
action?:
|
||||
| "chatButtonClicked"
|
||||
|
|
@ -100,6 +101,7 @@ export interface ExtensionMessage {
|
|||
label?: string
|
||||
}>
|
||||
error?: string
|
||||
info?: Record<string, any> // Environment information for bug reports
|
||||
}
|
||||
|
||||
export interface ApiConfigMeta {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ export interface WebviewMessage {
|
|||
| "language"
|
||||
| "maxReadFileLine"
|
||||
| "searchFiles"
|
||||
| "getBugReportInfo"
|
||||
| "openExternal"
|
||||
text?: string
|
||||
disabled?: boolean
|
||||
askResponse?: ClineAskResponse
|
||||
|
|
@ -141,6 +143,7 @@ export interface WebviewMessage {
|
|||
source?: "global" | "project"
|
||||
requestId?: string
|
||||
ids?: string[]
|
||||
url?: string // URL for openExternal message
|
||||
}
|
||||
|
||||
export const checkoutDiffPayloadSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { HTMLAttributes } from "react"
|
||||
import { HTMLAttributes, useState } from "react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { Trans } from "react-i18next"
|
||||
import { Info } from "lucide-react"
|
||||
import { Bug, Info } from "lucide-react"
|
||||
|
||||
import { VSCodeButton, VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ import { vscode } from "@/utils/vscode"
|
|||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
import { BugReportDialog } from "./BugReportDialog"
|
||||
import { Section } from "./Section"
|
||||
|
||||
type AboutProps = HTMLAttributes<HTMLDivElement> & {
|
||||
|
|
@ -21,6 +22,7 @@ type AboutProps = HTMLAttributes<HTMLDivElement> & {
|
|||
|
||||
export const About = ({ version, telemetrySetting, setTelemetrySetting, className, ...props }: AboutProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [showBugReportDialog, setShowBugReportDialog] = useState(false)
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)} {...props}>
|
||||
|
|
@ -73,7 +75,20 @@ export const About = ({ version, telemetrySetting, setTelemetrySetting, classNam
|
|||
{t("settings:footer.reset.button")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center gap-3">
|
||||
<p>{t("settings:footer.bugreport.description")}</p>
|
||||
<VSCodeButton
|
||||
onClick={() => setShowBugReportDialog(true)}
|
||||
appearance="secondary"
|
||||
className="shrink-0">
|
||||
<Bug className="w-4 h-4 text-vscode-foreground mr-1" />
|
||||
{t("settings:footer.bugreport.button")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{showBugReportDialog && <BugReportDialog onClose={() => setShowBugReportDialog(false)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
150
webview-ui/src/components/settings/BugReportDialog.tsx
Normal file
150
webview-ui/src/components/settings/BugReportDialog.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { Bug, ClipboardCopy, ExternalLink } from "lucide-react"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui"
|
||||
|
||||
interface BugReportDialogProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function BugReportDialog({ onClose }: BugReportDialogProps) {
|
||||
const { t } = useAppTranslation()
|
||||
const [environmentInfo, setEnvironmentInfo] = useState<Record<string, any> | null>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// Request environment info from extension
|
||||
vscode.postMessage({ type: "getBugReportInfo" })
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data
|
||||
if (message.type === "bugReportInfo" && message.info) {
|
||||
setEnvironmentInfo(message.info)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const formattedInfo = environmentInfo
|
||||
? Object.entries(environmentInfo)
|
||||
.map(([key, value]) => `- **${key}**: ${value}`)
|
||||
.join("\n")
|
||||
: ""
|
||||
|
||||
const handleCopy = () => {
|
||||
if (formattedInfo) {
|
||||
navigator.clipboard.writeText(formattedInfo)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateIssue = () => {
|
||||
if (!environmentInfo) return
|
||||
|
||||
// Build URL parameters to pre-fill GitHub issue template fields
|
||||
const params = new URLSearchParams()
|
||||
|
||||
// Add parameters based on the template's field IDs
|
||||
// version - The app version
|
||||
if (environmentInfo.appVersion) {
|
||||
params.append("version", environmentInfo.appVersion)
|
||||
}
|
||||
|
||||
// provider - Try to extract from API provider if available
|
||||
if (environmentInfo.apiProvider) {
|
||||
params.append("provider", environmentInfo.apiProvider)
|
||||
}
|
||||
|
||||
// model - Try to extract from model ID if available
|
||||
if (environmentInfo.modelId) {
|
||||
params.append("model", environmentInfo.modelId)
|
||||
}
|
||||
|
||||
// what-happened - Pre-fill with a template that includes environment info
|
||||
const environmentSummary = Object.entries(environmentInfo)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join("\n")
|
||||
|
||||
params.append(
|
||||
"what-happened",
|
||||
`I encountered an issue with Roo Code.\n\nEnvironment Information:\n${environmentSummary}`,
|
||||
)
|
||||
|
||||
// additional-context - Add any system-specific details
|
||||
if (environmentInfo.platform) {
|
||||
params.append(
|
||||
"additional-context",
|
||||
`Platform: ${environmentInfo.platform}\nVSCode: ${environmentInfo.vscodeVersion || "unknown"}`,
|
||||
)
|
||||
}
|
||||
|
||||
const issueUrl = `https://github.com/RooVetGit/Roo-Code/issues/new?template=bug_report.yml&${params.toString()}`
|
||||
|
||||
// Open URL
|
||||
vscode.postMessage({ type: "openExternal", url: issueUrl })
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bug className="w-5 h-5" />
|
||||
{t("settings:bugreport.title")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="mb-4">{t("settings:bugreport.description")}</p>
|
||||
|
||||
<div className="bg-vscode-editor-background rounded-md p-3 text-sm font-mono overflow-auto max-h-[300px] whitespace-pre-wrap">
|
||||
{environmentInfo ? (
|
||||
<pre>{formattedInfo}</pre>
|
||||
) : (
|
||||
<div className="animate-pulse flex space-x-4">
|
||||
<div className="flex-1 space-y-3 py-1">
|
||||
<div className="h-2 bg-vscode-editor-inactiveSelectionBackground rounded"></div>
|
||||
<div className="h-2 bg-vscode-editor-inactiveSelectionBackground rounded"></div>
|
||||
<div className="h-2 bg-vscode-editor-inactiveSelectionBackground rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex flex-row justify-between items-center gap-2">
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={handleCopy}
|
||||
disabled={!environmentInfo}
|
||||
className="flex items-center gap-1">
|
||||
<ClipboardCopy className="w-4 h-4 mr-1" />
|
||||
{copied ? t("settings:bugreport.copied") : t("settings:bugreport.copy")}
|
||||
</VSCodeButton>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<VSCodeButton appearance="secondary" onClick={onClose}>
|
||||
{t("settings:common.cancel")}
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={handleCreateIssue}
|
||||
disabled={!environmentInfo}
|
||||
className="flex items-center gap-1">
|
||||
<ExternalLink className="w-4 h-4 mr-1" />
|
||||
{t("settings:bugreport.createIssue")}
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
137
webview-ui/src/components/settings/__tests__/About.test.tsx
Normal file
137
webview-ui/src/components/settings/__tests__/About.test.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
|
||||
import { About } from "../About"
|
||||
import { TranslationContext } from "@/i18n/TranslationContext"
|
||||
import { TelemetrySetting } from "../../../../../src/shared/TelemetrySetting"
|
||||
|
||||
// Mock the BugReportDialog component
|
||||
jest.mock("../BugReportDialog", () => ({
|
||||
BugReportDialog: ({ onClose }: { onClose: () => void }) => (
|
||||
<div data-testid="bug-report-dialog">
|
||||
<button data-testid="close-dialog" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock lucide-react icons
|
||||
jest.mock("lucide-react", () => ({
|
||||
Info: () => <div data-testid="info-icon" />,
|
||||
Bug: () => <div data-testid="bug-icon" />,
|
||||
}))
|
||||
|
||||
// Mock VSCode components
|
||||
jest.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeButton: ({ children, onClick, appearance, className }: any) => (
|
||||
<button onClick={onClick} data-appearance={appearance} className={className} data-testid="vscode-button">
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
VSCodeCheckbox: ({ children, onChange, checked }: any) => (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
|
||||
data-testid="telemetry-checkbox"
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
),
|
||||
VSCodeLink: ({ children, href }: any) => <a href={href}>{children}</a>,
|
||||
}))
|
||||
|
||||
// Mock vscode API
|
||||
jest.mock("@/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockT = jest.fn((key) => key)
|
||||
const mockTranslationContext = {
|
||||
t: mockT,
|
||||
i18n: {} as any,
|
||||
}
|
||||
|
||||
describe("About component", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const renderComponent = (
|
||||
props: {
|
||||
version?: string
|
||||
telemetrySetting?: TelemetrySetting
|
||||
setTelemetrySetting?: (setting: TelemetrySetting) => void
|
||||
} = {},
|
||||
) => {
|
||||
const { version = "1.0.0", telemetrySetting = "unset", setTelemetrySetting = jest.fn() } = props
|
||||
|
||||
return render(
|
||||
<TranslationContext.Provider value={mockTranslationContext}>
|
||||
<About
|
||||
version={version}
|
||||
telemetrySetting={telemetrySetting}
|
||||
setTelemetrySetting={setTelemetrySetting}
|
||||
/>
|
||||
</TranslationContext.Provider>,
|
||||
)
|
||||
}
|
||||
|
||||
it("renders the version number", () => {
|
||||
renderComponent({ version: "1.2.3" })
|
||||
expect(mockT).toHaveBeenCalledWith("settings:sections.about")
|
||||
expect(screen.getByText(/Version: 1.2.3/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders the telemetry checkbox", () => {
|
||||
renderComponent()
|
||||
expect(screen.getByTestId("telemetry-checkbox")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders the bug report button", () => {
|
||||
renderComponent()
|
||||
const bugReportText = mockT("settings:footer.bugreport.button")
|
||||
expect(screen.getByText(bugReportText)).toBeInTheDocument()
|
||||
expect(screen.getByTestId("bug-icon")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("opens the bug report dialog when clicking the button", async () => {
|
||||
renderComponent()
|
||||
|
||||
// Bug report dialog shouldn't be visible initially
|
||||
expect(screen.queryByTestId("bug-report-dialog")).not.toBeInTheDocument()
|
||||
|
||||
// Click the report bug button
|
||||
const bugReportButton = screen.getByText(mockT("settings:footer.bugreport.button"))
|
||||
fireEvent.click(bugReportButton)
|
||||
|
||||
// Dialog should now be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("bug-report-dialog")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("closes the bug report dialog", async () => {
|
||||
renderComponent()
|
||||
|
||||
// Open dialog
|
||||
const bugReportButton = screen.getByText(mockT("settings:footer.bugreport.button"))
|
||||
fireEvent.click(bugReportButton)
|
||||
|
||||
// Dialog should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("bug-report-dialog")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close dialog
|
||||
const closeButton = screen.getByTestId("close-dialog")
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
// Dialog should be hidden
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("bug-report-dialog")).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
|
||||
import { BugReportDialog } from "../BugReportDialog"
|
||||
import { TranslationContext } from "@/i18n/TranslationContext"
|
||||
|
||||
// Mock vscode API
|
||||
const mockPostMessage = jest.fn()
|
||||
jest.mock("@/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: mockPostMessage,
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock window.addEventListener to capture message handler
|
||||
type MessageHandler = (event: any) => void
|
||||
const mockMessageHandlers: Record<string, MessageHandler> = {}
|
||||
const originalAddEventListener = window.addEventListener
|
||||
const originalRemoveEventListener = window.removeEventListener
|
||||
|
||||
beforeAll(() => {
|
||||
window.addEventListener = jest.fn((event, handler) => {
|
||||
if (event === "message" && typeof handler === "function") {
|
||||
mockMessageHandlers[event] = handler as MessageHandler
|
||||
}
|
||||
return originalAddEventListener(event, handler)
|
||||
})
|
||||
|
||||
window.removeEventListener = jest.fn((event, handler) => {
|
||||
if (event === "message" && typeof handler === "function" && mockMessageHandlers[event] === handler) {
|
||||
delete mockMessageHandlers[event]
|
||||
}
|
||||
return originalRemoveEventListener(event, handler)
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
window.addEventListener = originalAddEventListener
|
||||
window.removeEventListener = originalRemoveEventListener
|
||||
})
|
||||
|
||||
// Mock lucide-react icons
|
||||
jest.mock("lucide-react", () => ({
|
||||
Bug: () => <div data-testid="bug-icon" />,
|
||||
ClipboardCopy: () => <div data-testid="clipboard-icon" />,
|
||||
ExternalLink: () => <div data-testid="external-link-icon" />,
|
||||
}))
|
||||
|
||||
// Mock Dialog components
|
||||
jest.mock("@/components/ui", () => ({
|
||||
Dialog: ({ children, open, onOpenChange }: any) => (
|
||||
<div data-testid="dialog" data-open={open} onClick={() => onOpenChange && onOpenChange(false)}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DialogContent: ({ children, className }: any) => (
|
||||
<div data-testid="dialog-content" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DialogHeader: ({ children, className }: any) => (
|
||||
<div data-testid="dialog-header" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DialogTitle: ({ children, className }: any) => (
|
||||
<div data-testid="dialog-title" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
DialogFooter: ({ children, className }: any) => (
|
||||
<div data-testid="dialog-footer" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock VSCode components
|
||||
jest.mock("@vscode/webview-ui-toolkit/react", () => ({
|
||||
VSCodeButton: ({ children, onClick, appearance, disabled, className }: any) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
data-appearance={appearance}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
data-testid={
|
||||
appearance === "primary"
|
||||
? "create-issue-button"
|
||||
: appearance === "secondary"
|
||||
? "copy-button"
|
||||
: "default-button"
|
||||
}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock clipboard API
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: jest.fn(),
|
||||
},
|
||||
})
|
||||
|
||||
const mockT = jest.fn((key) => key)
|
||||
const mockTranslationContext = {
|
||||
t: mockT,
|
||||
i18n: {} as any,
|
||||
}
|
||||
|
||||
describe("BugReportDialog component", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const renderComponent = (props: { onClose?: () => void } = {}) => {
|
||||
const { onClose = jest.fn() } = props
|
||||
|
||||
return render(
|
||||
<TranslationContext.Provider value={mockTranslationContext}>
|
||||
<BugReportDialog onClose={onClose} />
|
||||
</TranslationContext.Provider>,
|
||||
)
|
||||
}
|
||||
|
||||
it("should render the dialog", () => {
|
||||
renderComponent()
|
||||
expect(screen.getByTestId("dialog")).toBeInTheDocument()
|
||||
expect(screen.getByTestId("dialog-content")).toBeInTheDocument()
|
||||
expect(screen.getByTestId("bug-icon")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should request environment info when mounted", () => {
|
||||
renderComponent()
|
||||
expect(mockPostMessage).toHaveBeenCalledWith({ type: "getBugReportInfo" })
|
||||
})
|
||||
|
||||
it("should display loading state initially", () => {
|
||||
renderComponent()
|
||||
expect(screen.getByTestId("create-issue-button")).toBeDisabled()
|
||||
expect(screen.getByTestId("copy-button")).toBeDisabled()
|
||||
expect(screen.getByTestId("dialog-content").textContent).toContain(mockT("settings:bugreport.description"))
|
||||
})
|
||||
|
||||
it("should display environment info when received", async () => {
|
||||
renderComponent()
|
||||
|
||||
// Simulate receiving environment info
|
||||
const envInfo = {
|
||||
vscodeVersion: "1.70.0",
|
||||
platform: "darwin",
|
||||
appVersion: "1.2.3",
|
||||
}
|
||||
|
||||
// Send a message with environment info
|
||||
if (mockMessageHandlers.message) {
|
||||
mockMessageHandlers.message({
|
||||
data: {
|
||||
type: "bugReportInfo",
|
||||
info: envInfo,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Should display the formatted info
|
||||
await waitFor(() => {
|
||||
const content = screen.getByTestId("dialog-content")
|
||||
expect(content.textContent).toContain("vscodeVersion")
|
||||
expect(content.textContent).toContain("platform")
|
||||
expect(content.textContent).toContain("appVersion")
|
||||
})
|
||||
|
||||
// Buttons should be enabled now
|
||||
expect(screen.getByTestId("create-issue-button")).not.toBeDisabled()
|
||||
expect(screen.getByTestId("copy-button")).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it("should copy environment info to clipboard", async () => {
|
||||
renderComponent()
|
||||
|
||||
// Simulate receiving environment info
|
||||
const envInfo = {
|
||||
vscodeVersion: "1.70.0",
|
||||
platform: "darwin",
|
||||
appVersion: "1.2.3",
|
||||
}
|
||||
|
||||
// Send a message with environment info
|
||||
if (mockMessageHandlers.message) {
|
||||
mockMessageHandlers.message({
|
||||
data: {
|
||||
type: "bugReportInfo",
|
||||
info: envInfo,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for info to be displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("copy-button")).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// Click copy button
|
||||
fireEvent.click(screen.getByTestId("copy-button"))
|
||||
|
||||
// Should call clipboard API
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||
expect.stringContaining("- **vscodeVersion**: 1.70.0"),
|
||||
)
|
||||
})
|
||||
|
||||
it("should create GitHub issue when button is clicked", async () => {
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
// Simulate receiving environment info
|
||||
const envInfo = {
|
||||
vscodeVersion: "1.70.0",
|
||||
platform: "darwin",
|
||||
appVersion: "1.2.3",
|
||||
}
|
||||
|
||||
// Send a message with environment info
|
||||
if (mockMessageHandlers.message) {
|
||||
mockMessageHandlers.message({
|
||||
data: {
|
||||
type: "bugReportInfo",
|
||||
info: envInfo,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for info to be displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("create-issue-button")).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// Click create issue button
|
||||
fireEvent.click(screen.getByTestId("create-issue-button"))
|
||||
|
||||
// Should post message to VS Code with URL containing environment info
|
||||
expect(mockPostMessage).toHaveBeenCalledWith({
|
||||
type: "openExternal",
|
||||
url: expect.stringContaining("https://github.com/RooVetGit/Roo-Code/issues/new?template=bug_report.yml"),
|
||||
})
|
||||
|
||||
// Verify URL contains the required parameters
|
||||
const urlArg = mockPostMessage.mock.calls[mockPostMessage.mock.calls.length - 1][0].url
|
||||
|
||||
// Check for version parameter
|
||||
expect(urlArg).toContain("version=1.2.3")
|
||||
|
||||
// Check for what-happened parameter with environment info
|
||||
expect(urlArg).toContain("what-happened=")
|
||||
expect(urlArg).toContain("Environment+Information")
|
||||
expect(urlArg).toContain("vscodeVersion")
|
||||
expect(urlArg).toContain("platform")
|
||||
expect(urlArg).toContain("appVersion")
|
||||
|
||||
// Check for additional-context parameter
|
||||
expect(urlArg).toContain("additional-context=")
|
||||
expect(urlArg).toContain("Platform%3A+darwin")
|
||||
|
||||
// Should call onClose
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should close when cancel button is clicked", () => {
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
// Find the cancel button (secondary button that isn't the copy button)
|
||||
const buttons = screen.getAllByRole("button")
|
||||
const cancelButton = buttons.find(
|
||||
(button) =>
|
||||
button !== screen.getByTestId("copy-button") && button !== screen.getByTestId("create-issue-button"),
|
||||
)
|
||||
|
||||
// Click cancel button
|
||||
if (cancelButton) {
|
||||
fireEvent.click(cancelButton)
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
} else {
|
||||
fail("Cancel button not found")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -372,6 +372,10 @@
|
|||
"reset": {
|
||||
"description": "Reset all global state and secret storage in the extension.",
|
||||
"button": "Reset"
|
||||
},
|
||||
"bugreport": {
|
||||
"description": "Found a bug? Report it to help us improve Roo Code.",
|
||||
"button": "Report Bug"
|
||||
}
|
||||
},
|
||||
"thinkingBudget": {
|
||||
|
|
@ -423,5 +427,12 @@
|
|||
"labels": {
|
||||
"customArn": "Custom ARN",
|
||||
"useCustomArn": "Use custom ARN..."
|
||||
},
|
||||
"bugreport": {
|
||||
"title": "Report a Bug",
|
||||
"description": "The following information will be included in your bug report to help us diagnose the issue:",
|
||||
"copy": "Copy Info",
|
||||
"copied": "Copied!",
|
||||
"createIssue": "Create GitHub Issue"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue