mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: ensure user messages are sent with save button for file editing operations
- Modified handlePrimaryButtonClick in ChatView to properly handle user input during file editing operations - For tool operations (file editing), the function now checks for current input from both the text parameter and inputValue state - This ensures that user messages typed while the agent is working are sent along with the save action - Added comprehensive tests for QueuedMessages component functionality - All existing tests continue to pass Fixes #6479
This commit is contained in:
parent
e13083e532
commit
2640abc186
2 changed files with 203 additions and 10 deletions
|
|
@ -723,19 +723,42 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
case "use_mcp_server":
|
||||
case "resume_task":
|
||||
case "mistake_limit_reached":
|
||||
// Only send text/images if they exist
|
||||
if (trimmedInput || (images && images.length > 0)) {
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
text: trimmedInput,
|
||||
images: images,
|
||||
})
|
||||
// For tool operations (like file editing), check if we have current input or queued messages
|
||||
if (clineAsk === "tool") {
|
||||
// Get current input from the text area (this includes any text typed while agent was working)
|
||||
const currentInput = text?.trim() || inputValue.trim()
|
||||
const currentImages = images || selectedImages
|
||||
|
||||
// Send the save action with any current input
|
||||
if (currentInput || (currentImages && currentImages.length > 0)) {
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
text: currentInput,
|
||||
images: currentImages,
|
||||
})
|
||||
} else {
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
|
||||
}
|
||||
|
||||
// Clear input state after sending
|
||||
setInputValue("")
|
||||
setSelectedImages([])
|
||||
} else {
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
|
||||
// For other operations, use the original logic
|
||||
if (trimmedInput || (images && images.length > 0)) {
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "yesButtonClicked",
|
||||
text: trimmedInput,
|
||||
images: images,
|
||||
})
|
||||
// Clear input state after sending
|
||||
setInputValue("")
|
||||
setSelectedImages([])
|
||||
} else {
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
|
||||
}
|
||||
}
|
||||
break
|
||||
case "completion_result":
|
||||
|
|
@ -752,7 +775,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
setClineAsk(undefined)
|
||||
setEnableButtons(false)
|
||||
},
|
||||
[clineAsk, startNewTask],
|
||||
[clineAsk, startNewTask, inputValue, selectedImages, setInputValue, setSelectedImages],
|
||||
)
|
||||
|
||||
const handleSecondaryButtonClick = useCallback(
|
||||
|
|
|
|||
170
webview-ui/src/components/chat/__tests__/QueuedMessages.spec.tsx
Normal file
170
webview-ui/src/components/chat/__tests__/QueuedMessages.spec.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import React from "react"
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
import { vi } from "vitest"
|
||||
import QueuedMessages from "../QueuedMessages"
|
||||
import { QueuedMessage } from "@roo-code/types"
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock the Mention component
|
||||
vi.mock("../Mention", () => ({
|
||||
Mention: ({ text }: { text: string }) => <span data-testid="mention">{text}</span>,
|
||||
}))
|
||||
|
||||
// Mock the Thumbnails component
|
||||
vi.mock("../common/Thumbnails", () => ({
|
||||
default: ({ images }: { images: string[] }) => <div data-testid="thumbnails">{images.length} images</div>,
|
||||
}))
|
||||
|
||||
// Mock the Button component
|
||||
vi.mock("@src/components/ui", () => ({
|
||||
Button: ({ children, onClick, ...props }: any) => (
|
||||
<button onClick={onClick} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
describe("QueuedMessages", () => {
|
||||
const mockOnRemove = vi.fn()
|
||||
const mockOnUpdate = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders nothing when queue is empty", () => {
|
||||
const { container } = render(<QueuedMessages queue={[]} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it("renders queued messages", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Test message 1",
|
||||
images: [],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
text: "Test message 2",
|
||||
images: ["image1.png"],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
expect(screen.getByTestId("queued-messages")).toBeInTheDocument()
|
||||
expect(screen.getByText("queuedMessages.title")).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId("mention")).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("calls onRemove when delete button is clicked", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Test message",
|
||||
images: [],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
const deleteButton = screen.getByRole("button")
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
expect(mockOnRemove).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it("enters edit mode when message is clicked", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Test message",
|
||||
images: [],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
const messageElement = screen.getByTestId("mention").parentElement
|
||||
fireEvent.click(messageElement!)
|
||||
|
||||
expect(screen.getByRole("textbox")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("calls onUpdate when edit is saved", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Test message",
|
||||
images: [],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
// Enter edit mode
|
||||
const messageElement = screen.getByTestId("mention").parentElement
|
||||
fireEvent.click(messageElement!)
|
||||
|
||||
// Edit the text
|
||||
const textarea = screen.getByRole("textbox")
|
||||
fireEvent.change(textarea, { target: { value: "Updated message" } })
|
||||
|
||||
// Save by pressing Enter
|
||||
fireEvent.keyDown(textarea, { key: "Enter" })
|
||||
|
||||
expect(mockOnUpdate).toHaveBeenCalledWith(0, "Updated message")
|
||||
})
|
||||
|
||||
it("cancels edit when Escape is pressed", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Test message",
|
||||
images: [],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
// Enter edit mode
|
||||
const messageElement = screen.getByTestId("mention").parentElement
|
||||
fireEvent.click(messageElement!)
|
||||
|
||||
// Edit the text
|
||||
const textarea = screen.getByRole("textbox")
|
||||
fireEvent.change(textarea, { target: { value: "Updated message" } })
|
||||
|
||||
// Cancel by pressing Escape
|
||||
fireEvent.keyDown(textarea, { key: "Escape" })
|
||||
|
||||
// Should not call onUpdate and should exit edit mode
|
||||
expect(mockOnUpdate).not.toHaveBeenCalled()
|
||||
expect(screen.queryByRole("textbox")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("renders thumbnails for messages with images", () => {
|
||||
const queue: QueuedMessage[] = [
|
||||
{
|
||||
id: "1",
|
||||
text: "Message with images",
|
||||
images: ["image1.png", "image2.png"],
|
||||
},
|
||||
]
|
||||
|
||||
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
|
||||
|
||||
// Check that images are rendered (the actual Thumbnails component renders img elements)
|
||||
const images = screen.getAllByRole("img")
|
||||
expect(images).toHaveLength(2)
|
||||
expect(images[0]).toHaveAttribute("src", "image1.png")
|
||||
expect(images[1]).toHaveAttribute("src", "image2.png")
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue