mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Merge pull request #636 from samhvw8/feat/vite-tailwind
refactor: migrate from CRA to Vite and improve testing
This commit is contained in:
117
webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts
Normal file
117
webview-ui/src/__mocks__/@vscode/webview-ui-toolkit/react.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import React from "react"
|
||||
|
||||
interface VSCodeProps {
|
||||
children?: React.ReactNode
|
||||
onClick?: () => void
|
||||
onChange?: (e: any) => void
|
||||
onInput?: (e: any) => void
|
||||
appearance?: string
|
||||
checked?: boolean
|
||||
value?: string | number
|
||||
placeholder?: string
|
||||
href?: string
|
||||
"data-testid"?: string
|
||||
style?: React.CSSProperties
|
||||
slot?: string
|
||||
role?: string
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export const VSCodeButton: React.FC<VSCodeProps> = ({ children, onClick, appearance, className, ...props }) => {
|
||||
// For icon buttons, render children directly without any wrapping
|
||||
if (appearance === "icon") {
|
||||
return React.createElement(
|
||||
"button",
|
||||
{
|
||||
onClick,
|
||||
className: `${className || ""}`,
|
||||
"data-appearance": appearance,
|
||||
...props,
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
// For regular buttons
|
||||
return React.createElement(
|
||||
"button",
|
||||
{
|
||||
onClick,
|
||||
className: className,
|
||||
...props,
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
export const VSCodeCheckbox: React.FC<VSCodeProps> = ({ children, onChange, checked, ...props }) =>
|
||||
React.createElement("label", {}, [
|
||||
React.createElement("input", {
|
||||
key: "input",
|
||||
type: "checkbox",
|
||||
checked,
|
||||
onChange: (e: any) => onChange?.({ target: { checked: e.target.checked } }),
|
||||
"aria-label": typeof children === "string" ? children : undefined,
|
||||
...props,
|
||||
}),
|
||||
children && React.createElement("span", { key: "label" }, children),
|
||||
])
|
||||
|
||||
export const VSCodeTextField: React.FC<VSCodeProps> = ({ children, value, onInput, placeholder, ...props }) =>
|
||||
React.createElement("div", { style: { position: "relative", display: "inline-block", width: "100%" } }, [
|
||||
React.createElement("input", {
|
||||
key: "input",
|
||||
type: "text",
|
||||
value,
|
||||
onChange: (e: any) => onInput?.({ target: { value: e.target.value } }),
|
||||
placeholder,
|
||||
...props,
|
||||
}),
|
||||
children,
|
||||
])
|
||||
|
||||
export const VSCodeTextArea: React.FC<VSCodeProps> = ({ value, onChange, ...props }) =>
|
||||
React.createElement("textarea", {
|
||||
value,
|
||||
onChange: (e: any) => onChange?.({ target: { value: e.target.value } }),
|
||||
...props,
|
||||
})
|
||||
|
||||
export const VSCodeLink: React.FC<VSCodeProps> = ({ children, href, ...props }) =>
|
||||
React.createElement("a", { href: href || "#", ...props }, children)
|
||||
|
||||
export const VSCodeDropdown: React.FC<VSCodeProps> = ({ children, value, onChange, ...props }) =>
|
||||
React.createElement("select", { value, onChange, ...props }, children)
|
||||
|
||||
export const VSCodeOption: React.FC<VSCodeProps> = ({ children, value, ...props }) =>
|
||||
React.createElement("option", { value, ...props }, children)
|
||||
|
||||
export const VSCodeRadio: React.FC<VSCodeProps> = ({ children, value, checked, onChange, ...props }) =>
|
||||
React.createElement("label", { style: { display: "inline-flex", alignItems: "center" } }, [
|
||||
React.createElement("input", {
|
||||
key: "input",
|
||||
type: "radio",
|
||||
value,
|
||||
checked,
|
||||
onChange,
|
||||
...props,
|
||||
}),
|
||||
children && React.createElement("span", { key: "label", style: { marginLeft: "4px" } }, children),
|
||||
])
|
||||
|
||||
export const VSCodeRadioGroup: React.FC<VSCodeProps> = ({ children, onChange, ...props }) =>
|
||||
React.createElement("div", { role: "radiogroup", onChange, ...props }, children)
|
||||
|
||||
export const VSCodeSlider: React.FC<VSCodeProps> = ({ value, onChange, ...props }) =>
|
||||
React.createElement("input", {
|
||||
type: "range",
|
||||
value,
|
||||
onChange: (e: any) => onChange?.({ target: { value: Number(e.target.value) } }),
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
style: { flexGrow: 1, height: "2px" },
|
||||
...props,
|
||||
})
|
||||
14
webview-ui/src/__mocks__/vscrui.ts
Normal file
14
webview-ui/src/__mocks__/vscrui.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react"
|
||||
|
||||
export const Checkbox = ({ children, checked, onChange }: any) =>
|
||||
React.createElement("div", { "data-testid": "mock-checkbox", onClick: onChange }, children)
|
||||
|
||||
export const Dropdown = ({ children, value, onChange }: any) =>
|
||||
React.createElement("div", { "data-testid": "mock-dropdown", onClick: onChange }, children)
|
||||
|
||||
export const Pane = ({ children }: any) => React.createElement("div", { "data-testid": "mock-pane" }, children)
|
||||
|
||||
export type DropdownOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
@@ -37,9 +37,9 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
const isLastApiReqInterrupted = useMemo(() => {
|
||||
// Check if last api_req_started is cancelled
|
||||
const lastApiReqStarted = [...messages].reverse().find((m) => m.say === "api_req_started")
|
||||
if (lastApiReqStarted?.text != null) {
|
||||
const info = JSON.parse(lastApiReqStarted.text)
|
||||
if (info.cancelReason != null) {
|
||||
if (lastApiReqStarted?.text) {
|
||||
const info = JSON.parse(lastApiReqStarted.text) as { cancelReason: string | null }
|
||||
if (info && info.cancelReason !== null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { render, fireEvent, screen } from "@testing-library/react"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import AutoApproveMenu from "../AutoApproveMenu"
|
||||
import { defaultModeSlug, defaultPrompts } from "../../../../../src/shared/modes"
|
||||
import { experimentDefault } from "../../../../../src/shared/experiments"
|
||||
|
||||
// Mock the ExtensionStateContext hook
|
||||
jest.mock("../../../context/ExtensionStateContext")
|
||||
@@ -41,6 +42,8 @@ describe("AutoApproveMenu", () => {
|
||||
openAiModels: [],
|
||||
mcpServers: [],
|
||||
filePaths: [],
|
||||
experiments: experimentDefault,
|
||||
customModes: [],
|
||||
|
||||
// Auto-approve specific properties
|
||||
alwaysAllowReadOnly: false,
|
||||
@@ -49,6 +52,7 @@ describe("AutoApproveMenu", () => {
|
||||
alwaysAllowBrowser: false,
|
||||
alwaysAllowMcp: false,
|
||||
alwaysApproveResubmit: false,
|
||||
alwaysAllowModeSwitch: false,
|
||||
autoApprovalEnabled: false,
|
||||
|
||||
// Required setter functions
|
||||
@@ -59,6 +63,7 @@ describe("AutoApproveMenu", () => {
|
||||
setAlwaysAllowExecute: jest.fn(),
|
||||
setAlwaysAllowBrowser: jest.fn(),
|
||||
setAlwaysAllowMcp: jest.fn(),
|
||||
setAlwaysAllowModeSwitch: jest.fn(),
|
||||
setShowAnnouncement: jest.fn(),
|
||||
setAllowedCommands: jest.fn(),
|
||||
setSoundEnabled: jest.fn(),
|
||||
@@ -77,9 +82,13 @@ describe("AutoApproveMenu", () => {
|
||||
setListApiConfigMeta: jest.fn(),
|
||||
onUpdateApiConfig: jest.fn(),
|
||||
setMode: jest.fn(),
|
||||
setCustomPrompts: jest.fn(),
|
||||
setCustomModePrompts: jest.fn(),
|
||||
setCustomSupportPrompts: jest.fn(),
|
||||
setEnhancementApiConfigId: jest.fn(),
|
||||
setAutoApprovalEnabled: jest.fn(),
|
||||
setExperimentEnabled: jest.fn(),
|
||||
handleInputChange: jest.fn(),
|
||||
setCustomModes: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -16,7 +16,6 @@ jest.mock("../../../components/common/MarkdownBlock")
|
||||
|
||||
// Get the mocked postMessage function
|
||||
const mockPostMessage = vscode.postMessage as jest.Mock
|
||||
/* eslint-enable import/first */
|
||||
|
||||
// Mock ExtensionStateContext
|
||||
jest.mock("../../../context/ExtensionStateContext")
|
||||
|
||||
@@ -202,6 +202,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
<VSCodeRadioGroup
|
||||
style={{ display: "flex", flexWrap: "wrap" }}
|
||||
value={sortOption}
|
||||
role="radiogroup"
|
||||
onChange={(e) => setSortOption((e.target as HTMLInputElement).value as SortOption)}>
|
||||
<VSCodeRadio value="newest">Newest</VSCodeRadio>
|
||||
<VSCodeRadio value="oldest">Oldest</VSCodeRadio>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
|
||||
import "@testing-library/jest-dom"
|
||||
import PromptsView from "../PromptsView"
|
||||
import { ExtensionStateContext } from "../../../context/ExtensionStateContext"
|
||||
@@ -98,20 +98,14 @@ describe("PromptsView", () => {
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
})
|
||||
|
||||
it("handles prompt changes correctly", () => {
|
||||
it("handles prompt changes correctly", async () => {
|
||||
renderPromptsView()
|
||||
|
||||
const textarea = screen.getByTestId("code-prompt-textarea")
|
||||
fireEvent(
|
||||
textarea,
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
target: {
|
||||
value: "New prompt value",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
// Get the textarea
|
||||
const textarea = await waitFor(() => screen.getByTestId("code-prompt-textarea"))
|
||||
fireEvent.change(textarea, {
|
||||
target: { value: "New prompt value" },
|
||||
})
|
||||
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updatePrompt",
|
||||
@@ -163,24 +157,18 @@ describe("PromptsView", () => {
|
||||
expect(screen.queryByTestId("role-definition-reset")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("handles API configuration selection", () => {
|
||||
it("handles API configuration selection", async () => {
|
||||
renderPromptsView()
|
||||
|
||||
// Click the ENHANCE tab first to show the API config dropdown
|
||||
const enhanceTab = screen.getByTestId("ENHANCE-tab")
|
||||
fireEvent.click(enhanceTab)
|
||||
|
||||
const dropdown = screen.getByTestId("api-config-dropdown")
|
||||
fireEvent(
|
||||
dropdown,
|
||||
new CustomEvent("change", {
|
||||
detail: {
|
||||
target: {
|
||||
value: "config1",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
// Wait for the ENHANCE tab click to take effect
|
||||
const dropdown = await waitFor(() => screen.getByTestId("api-config-dropdown"))
|
||||
fireEvent.change(dropdown, {
|
||||
target: { value: "config1" },
|
||||
})
|
||||
|
||||
expect(mockExtensionState.setEnhancementApiConfigId).toHaveBeenCalledWith("config1")
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
@@ -198,13 +186,9 @@ describe("PromptsView", () => {
|
||||
})
|
||||
|
||||
const textarea = screen.getByTestId("global-custom-instructions-textarea")
|
||||
const changeEvent = new CustomEvent("change", {
|
||||
detail: { target: { value: "" } },
|
||||
fireEvent.change(textarea, {
|
||||
target: { value: "" },
|
||||
})
|
||||
Object.defineProperty(changeEvent, "target", {
|
||||
value: { value: "" },
|
||||
})
|
||||
await fireEvent(textarea, changeEvent)
|
||||
|
||||
expect(setCustomInstructions).toHaveBeenCalledWith(undefined)
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
|
||||
@@ -41,7 +41,10 @@ describe("ApiConfigManager", () => {
|
||||
|
||||
const defaultProps = {
|
||||
currentApiConfigName: "Default Config",
|
||||
listApiConfigMeta: [{ name: "Default Config" }, { name: "Another Config" }],
|
||||
listApiConfigMeta: [
|
||||
{ id: "default", name: "Default Config" },
|
||||
{ id: "another", name: "Another Config" },
|
||||
],
|
||||
onSelectConfig: mockOnSelectConfig,
|
||||
onDeleteConfig: mockOnDeleteConfig,
|
||||
onRenameConfig: mockOnRenameConfig,
|
||||
@@ -120,7 +123,7 @@ describe("ApiConfigManager", () => {
|
||||
})
|
||||
|
||||
it("disables delete button when only one config exists", () => {
|
||||
render(<ApiConfigManager {...defaultProps} listApiConfigMeta={[{ name: "Default Config" }]} />)
|
||||
render(<ApiConfigManager {...defaultProps} listApiConfigMeta={[{ id: "default", name: "Default Config" }]} />)
|
||||
|
||||
const deleteButton = screen.getByTitle("Cannot delete the only profile")
|
||||
expect(deleteButton).toHaveAttribute("disabled")
|
||||
|
||||
@@ -1,3 +1,67 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
/* Theme Variables - VSCode Integration */
|
||||
:root {
|
||||
/* Base Colors */
|
||||
--background: var(--vscode-editor-background);
|
||||
--foreground: var(--vscode-editor-foreground);
|
||||
|
||||
/* Component Colors */
|
||||
--card: var(--vscode-editor-background);
|
||||
--card-foreground: var(--vscode-editor-foreground);
|
||||
--popover: var(--vscode-menu-background, var(--vscode-editor-background));
|
||||
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
|
||||
|
||||
/* Button Colors */
|
||||
--primary: var(--vscode-button-background);
|
||||
--primary-foreground: var(--vscode-button-foreground);
|
||||
--secondary: var(--vscode-button-secondaryBackground);
|
||||
--secondary-foreground: var(--vscode-button-secondaryForeground);
|
||||
--accent: var(--vscode-focusBorder);
|
||||
--accent-foreground: var(--vscode-button-foreground);
|
||||
|
||||
/* State Colors */
|
||||
--muted: var(--vscode-disabledForeground);
|
||||
--muted-foreground: var(--vscode-descriptionForeground);
|
||||
--destructive: var(--vscode-errorForeground);
|
||||
--destructive-foreground: var(--vscode-editor-background);
|
||||
|
||||
/* UI Elements */
|
||||
--border: var(--vscode-widget-border);
|
||||
--input: var(--vscode-input-background);
|
||||
--ring: var(--vscode-focusBorder);
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Chart Colors - Using VSCode's chart colors */
|
||||
--chart-1: var(--vscode-charts-red);
|
||||
--chart-2: var(--vscode-charts-blue);
|
||||
--chart-3: var(--vscode-charts-yellow);
|
||||
--chart-4: var(--vscode-charts-orange);
|
||||
--chart-5: var(--vscode-charts-green);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Border Styles */
|
||||
.border,
|
||||
.border-r,
|
||||
.border-l,
|
||||
.border-t,
|
||||
.border-b,
|
||||
.border-x,
|
||||
.border-y {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
/* Code Block Styles */
|
||||
pre,
|
||||
code {
|
||||
background-color: var(--vscode-textCodeBlock-background);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Element Focus States */
|
||||
textarea:focus {
|
||||
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react"
|
||||
import ReactDOM from "react-dom/client"
|
||||
import "./index.css"
|
||||
import App from "./App"
|
||||
import reportWebVitals from "./reportWebVitals"
|
||||
import "../../node_modules/@vscode/codicons/dist/codicon.css"
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
@@ -11,8 +10,3 @@ root.render(
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals()
|
||||
|
||||
1
webview-ui/src/react-app-env.d.ts
vendored
1
webview-ui/src/react-app-env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1,15 +0,0 @@
|
||||
import { ReportHandler } from "web-vitals"
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry)
|
||||
getFID(onPerfEntry)
|
||||
getFCP(onPerfEntry)
|
||||
getLCP(onPerfEntry)
|
||||
getTTFB(onPerfEntry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default reportWebVitals
|
||||
Reference in New Issue
Block a user