mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Merge pull request #1025 from RooVetGit/cte/app-tabs
This commit is contained in:
commit
f3d4c37b2d
2 changed files with 247 additions and 79 deletions
|
|
@ -1,64 +1,45 @@
|
|||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useEvent } from "react-use"
|
||||
|
||||
import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
|
||||
|
||||
import { vscode } from "./utils/vscode"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
|
||||
import ChatView from "./components/chat/ChatView"
|
||||
import HistoryView from "./components/history/HistoryView"
|
||||
import SettingsView from "./components/settings/SettingsView"
|
||||
import WelcomeView from "./components/welcome/WelcomeView"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
|
||||
import { vscode } from "./utils/vscode"
|
||||
import McpView from "./components/mcp/McpView"
|
||||
import PromptsView from "./components/prompts/PromptsView"
|
||||
|
||||
const AppContent = () => {
|
||||
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const [showMcp, setShowMcp] = useState(false)
|
||||
const [showPrompts, setShowPrompts] = useState(false)
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false)
|
||||
type Tab = "settings" | "history" | "mcp" | "prompts" | "chat"
|
||||
|
||||
const handleMessage = useCallback((e: MessageEvent) => {
|
||||
const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]>, Tab>> = {
|
||||
chatButtonClicked: "chat",
|
||||
settingsButtonClicked: "settings",
|
||||
promptsButtonClicked: "prompts",
|
||||
mcpButtonClicked: "mcp",
|
||||
historyButtonClicked: "history",
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false)
|
||||
const [tab, setTab] = useState<Tab>("chat")
|
||||
|
||||
const onMessage = useCallback((e: MessageEvent) => {
|
||||
const message: ExtensionMessage = e.data
|
||||
switch (message.type) {
|
||||
case "action":
|
||||
switch (message.action!) {
|
||||
case "settingsButtonClicked":
|
||||
setShowSettings(true)
|
||||
setShowHistory(false)
|
||||
setShowMcp(false)
|
||||
setShowPrompts(false)
|
||||
break
|
||||
case "historyButtonClicked":
|
||||
setShowSettings(false)
|
||||
setShowHistory(true)
|
||||
setShowMcp(false)
|
||||
setShowPrompts(false)
|
||||
break
|
||||
case "mcpButtonClicked":
|
||||
setShowSettings(false)
|
||||
setShowHistory(false)
|
||||
setShowMcp(true)
|
||||
setShowPrompts(false)
|
||||
break
|
||||
case "promptsButtonClicked":
|
||||
setShowSettings(false)
|
||||
setShowHistory(false)
|
||||
setShowMcp(false)
|
||||
setShowPrompts(true)
|
||||
break
|
||||
case "chatButtonClicked":
|
||||
setShowSettings(false)
|
||||
setShowHistory(false)
|
||||
setShowMcp(false)
|
||||
setShowPrompts(false)
|
||||
break
|
||||
}
|
||||
break
|
||||
|
||||
if (message.type === "action" && message.action) {
|
||||
const newTab = tabsByMessageAction[message.action]
|
||||
|
||||
if (newTab) {
|
||||
setTab(newTab)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
useEvent("message", onMessage)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowAnnouncement) {
|
||||
|
|
@ -71,42 +52,30 @@ const AppContent = () => {
|
|||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
// Do not conditionally load ChatView, it's expensive and there's state we
|
||||
// don't want to lose (user input, disableInput, askResponse promise, etc.)
|
||||
return showWelcome ? (
|
||||
<WelcomeView />
|
||||
) : (
|
||||
<>
|
||||
{showWelcome ? (
|
||||
<WelcomeView />
|
||||
) : (
|
||||
<>
|
||||
{showSettings && <SettingsView onDone={() => setShowSettings(false)} />}
|
||||
{showHistory && <HistoryView onDone={() => setShowHistory(false)} />}
|
||||
{showMcp && <McpView onDone={() => setShowMcp(false)} />}
|
||||
{showPrompts && <PromptsView onDone={() => setShowPrompts(false)} />}
|
||||
{/* Do not conditionally load ChatView, it's expensive and there's state we don't want to lose (user input, disableInput, askResponse promise, etc.) */}
|
||||
<ChatView
|
||||
showHistoryView={() => {
|
||||
setShowSettings(false)
|
||||
setShowMcp(false)
|
||||
setShowPrompts(false)
|
||||
setShowHistory(true)
|
||||
}}
|
||||
isHidden={showSettings || showHistory || showMcp || showPrompts}
|
||||
showAnnouncement={showAnnouncement}
|
||||
hideAnnouncement={() => {
|
||||
setShowAnnouncement(false)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{tab === "settings" && <SettingsView onDone={() => setTab("chat")} />}
|
||||
{tab === "history" && <HistoryView onDone={() => setTab("chat")} />}
|
||||
{tab === "mcp" && <McpView onDone={() => setTab("chat")} />}
|
||||
{tab === "prompts" && <PromptsView onDone={() => setTab("chat")} />}
|
||||
<ChatView
|
||||
isHidden={tab !== "chat"}
|
||||
showAnnouncement={showAnnouncement}
|
||||
hideAnnouncement={() => setShowAnnouncement(false)}
|
||||
showHistoryView={() => setTab("history")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ExtensionStateContextProvider>
|
||||
<AppContent />
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
}
|
||||
const AppWithProviders = () => (
|
||||
<ExtensionStateContextProvider>
|
||||
<App />
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
|
||||
export default App
|
||||
export default AppWithProviders
|
||||
|
|
|
|||
199
webview-ui/src/__tests__/App.test.tsx
Normal file
199
webview-ui/src/__tests__/App.test.tsx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// npx jest src/__tests__/App.test.tsx
|
||||
|
||||
import React from "react"
|
||||
import { render, screen, act, cleanup } from "@testing-library/react"
|
||||
import "@testing-library/jest-dom"
|
||||
|
||||
import AppWithProviders from "../App"
|
||||
|
||||
jest.mock("../utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../components/chat/ChatView", () => ({
|
||||
__esModule: true,
|
||||
default: function ChatView({ isHidden }: { isHidden: boolean }) {
|
||||
return (
|
||||
<div data-testid="chat-view" data-hidden={isHidden}>
|
||||
Chat View
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../components/settings/SettingsView", () => ({
|
||||
__esModule: true,
|
||||
default: function SettingsView({ onDone }: { onDone: () => void }) {
|
||||
return (
|
||||
<div data-testid="settings-view" onClick={onDone}>
|
||||
Settings View
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../components/history/HistoryView", () => ({
|
||||
__esModule: true,
|
||||
default: function HistoryView({ onDone }: { onDone: () => void }) {
|
||||
return (
|
||||
<div data-testid="history-view" onClick={onDone}>
|
||||
History View
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../components/mcp/McpView", () => ({
|
||||
__esModule: true,
|
||||
default: function McpView({ onDone }: { onDone: () => void }) {
|
||||
return (
|
||||
<div data-testid="mcp-view" onClick={onDone}>
|
||||
MCP View
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../components/prompts/PromptsView", () => ({
|
||||
__esModule: true,
|
||||
default: function PromptsView({ onDone }: { onDone: () => void }) {
|
||||
return (
|
||||
<div data-testid="prompts-view" onClick={onDone}>
|
||||
Prompts View
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock("../context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
didHydrateState: true,
|
||||
showWelcome: false,
|
||||
shouldShowAnnouncement: false,
|
||||
}),
|
||||
ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}))
|
||||
|
||||
describe("App", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
window.removeEventListener("message", () => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
window.removeEventListener("message", () => {})
|
||||
})
|
||||
|
||||
const triggerMessage = (action: string) => {
|
||||
const messageEvent = new MessageEvent("message", {
|
||||
data: {
|
||||
type: "action",
|
||||
action,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(messageEvent)
|
||||
}
|
||||
|
||||
it("shows chat view by default", () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView).toBeInTheDocument()
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("false")
|
||||
})
|
||||
|
||||
it("switches to settings view when receiving settingsButtonClicked action", async () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage("settingsButtonClicked")
|
||||
})
|
||||
|
||||
const settingsView = await screen.findByTestId("settings-view")
|
||||
expect(settingsView).toBeInTheDocument()
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("true")
|
||||
})
|
||||
|
||||
it("switches to history view when receiving historyButtonClicked action", async () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage("historyButtonClicked")
|
||||
})
|
||||
|
||||
const historyView = await screen.findByTestId("history-view")
|
||||
expect(historyView).toBeInTheDocument()
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("true")
|
||||
})
|
||||
|
||||
it("switches to MCP view when receiving mcpButtonClicked action", async () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage("mcpButtonClicked")
|
||||
})
|
||||
|
||||
const mcpView = await screen.findByTestId("mcp-view")
|
||||
expect(mcpView).toBeInTheDocument()
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("true")
|
||||
})
|
||||
|
||||
it("switches to prompts view when receiving promptsButtonClicked action", async () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage("promptsButtonClicked")
|
||||
})
|
||||
|
||||
const promptsView = await screen.findByTestId("prompts-view")
|
||||
expect(promptsView).toBeInTheDocument()
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("true")
|
||||
})
|
||||
|
||||
it("returns to chat view when clicking done in settings view", async () => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage("settingsButtonClicked")
|
||||
})
|
||||
|
||||
const settingsView = await screen.findByTestId("settings-view")
|
||||
|
||||
act(() => {
|
||||
settingsView.click()
|
||||
})
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("false")
|
||||
expect(screen.queryByTestId("settings-view")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.each(["history", "mcp", "prompts"])("returns to chat view when clicking done in %s view", async (view) => {
|
||||
render(<AppWithProviders />)
|
||||
|
||||
act(() => {
|
||||
triggerMessage(`${view}ButtonClicked`)
|
||||
})
|
||||
|
||||
const viewElement = await screen.findByTestId(`${view}-view`)
|
||||
|
||||
act(() => {
|
||||
viewElement.click()
|
||||
})
|
||||
|
||||
const chatView = screen.getByTestId("chat-view")
|
||||
expect(chatView.getAttribute("data-hidden")).toBe("false")
|
||||
expect(screen.queryByTestId(`${view}-view`)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue