Skip to content

Commit 2640abc

Browse files
committed
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
1 parent e13083e commit 2640abc

File tree

2 files changed

+203
-10
lines changed

2 files changed

+203
-10
lines changed

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -723,19 +723,42 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
723723
case "use_mcp_server":
724724
case "resume_task":
725725
case "mistake_limit_reached":
726-
// Only send text/images if they exist
727-
if (trimmedInput || (images && images.length > 0)) {
728-
vscode.postMessage({
729-
type: "askResponse",
730-
askResponse: "yesButtonClicked",
731-
text: trimmedInput,
732-
images: images,
733-
})
726+
// For tool operations (like file editing), check if we have current input or queued messages
727+
if (clineAsk === "tool") {
728+
// Get current input from the text area (this includes any text typed while agent was working)
729+
const currentInput = text?.trim() || inputValue.trim()
730+
const currentImages = images || selectedImages
731+
732+
// Send the save action with any current input
733+
if (currentInput || (currentImages && currentImages.length > 0)) {
734+
vscode.postMessage({
735+
type: "askResponse",
736+
askResponse: "yesButtonClicked",
737+
text: currentInput,
738+
images: currentImages,
739+
})
740+
} else {
741+
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
742+
}
743+
734744
// Clear input state after sending
735745
setInputValue("")
736746
setSelectedImages([])
737747
} else {
738-
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
748+
// For other operations, use the original logic
749+
if (trimmedInput || (images && images.length > 0)) {
750+
vscode.postMessage({
751+
type: "askResponse",
752+
askResponse: "yesButtonClicked",
753+
text: trimmedInput,
754+
images: images,
755+
})
756+
// Clear input state after sending
757+
setInputValue("")
758+
setSelectedImages([])
759+
} else {
760+
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
761+
}
739762
}
740763
break
741764
case "completion_result":
@@ -752,7 +775,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
752775
setClineAsk(undefined)
753776
setEnableButtons(false)
754777
},
755-
[clineAsk, startNewTask],
778+
[clineAsk, startNewTask, inputValue, selectedImages, setInputValue, setSelectedImages],
756779
)
757780

758781
const handleSecondaryButtonClick = useCallback(
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import React from "react"
2+
import { render, screen, fireEvent } from "@testing-library/react"
3+
import { vi } from "vitest"
4+
import QueuedMessages from "../QueuedMessages"
5+
import { QueuedMessage } from "@roo-code/types"
6+
7+
// Mock react-i18next
8+
vi.mock("react-i18next", () => ({
9+
useTranslation: () => ({
10+
t: (key: string) => key,
11+
}),
12+
}))
13+
14+
// Mock the Mention component
15+
vi.mock("../Mention", () => ({
16+
Mention: ({ text }: { text: string }) => <span data-testid="mention">{text}</span>,
17+
}))
18+
19+
// Mock the Thumbnails component
20+
vi.mock("../common/Thumbnails", () => ({
21+
default: ({ images }: { images: string[] }) => <div data-testid="thumbnails">{images.length} images</div>,
22+
}))
23+
24+
// Mock the Button component
25+
vi.mock("@src/components/ui", () => ({
26+
Button: ({ children, onClick, ...props }: any) => (
27+
<button onClick={onClick} {...props}>
28+
{children}
29+
</button>
30+
),
31+
}))
32+
33+
describe("QueuedMessages", () => {
34+
const mockOnRemove = vi.fn()
35+
const mockOnUpdate = vi.fn()
36+
37+
beforeEach(() => {
38+
vi.clearAllMocks()
39+
})
40+
41+
it("renders nothing when queue is empty", () => {
42+
const { container } = render(<QueuedMessages queue={[]} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
43+
expect(container.firstChild).toBeNull()
44+
})
45+
46+
it("renders queued messages", () => {
47+
const queue: QueuedMessage[] = [
48+
{
49+
id: "1",
50+
text: "Test message 1",
51+
images: [],
52+
},
53+
{
54+
id: "2",
55+
text: "Test message 2",
56+
images: ["image1.png"],
57+
},
58+
]
59+
60+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
61+
62+
expect(screen.getByTestId("queued-messages")).toBeInTheDocument()
63+
expect(screen.getByText("queuedMessages.title")).toBeInTheDocument()
64+
expect(screen.getAllByTestId("mention")).toHaveLength(2)
65+
})
66+
67+
it("calls onRemove when delete button is clicked", () => {
68+
const queue: QueuedMessage[] = [
69+
{
70+
id: "1",
71+
text: "Test message",
72+
images: [],
73+
},
74+
]
75+
76+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
77+
78+
const deleteButton = screen.getByRole("button")
79+
fireEvent.click(deleteButton)
80+
81+
expect(mockOnRemove).toHaveBeenCalledWith(0)
82+
})
83+
84+
it("enters edit mode when message is clicked", () => {
85+
const queue: QueuedMessage[] = [
86+
{
87+
id: "1",
88+
text: "Test message",
89+
images: [],
90+
},
91+
]
92+
93+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
94+
95+
const messageElement = screen.getByTestId("mention").parentElement
96+
fireEvent.click(messageElement!)
97+
98+
expect(screen.getByRole("textbox")).toBeInTheDocument()
99+
})
100+
101+
it("calls onUpdate when edit is saved", () => {
102+
const queue: QueuedMessage[] = [
103+
{
104+
id: "1",
105+
text: "Test message",
106+
images: [],
107+
},
108+
]
109+
110+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
111+
112+
// Enter edit mode
113+
const messageElement = screen.getByTestId("mention").parentElement
114+
fireEvent.click(messageElement!)
115+
116+
// Edit the text
117+
const textarea = screen.getByRole("textbox")
118+
fireEvent.change(textarea, { target: { value: "Updated message" } })
119+
120+
// Save by pressing Enter
121+
fireEvent.keyDown(textarea, { key: "Enter" })
122+
123+
expect(mockOnUpdate).toHaveBeenCalledWith(0, "Updated message")
124+
})
125+
126+
it("cancels edit when Escape is pressed", () => {
127+
const queue: QueuedMessage[] = [
128+
{
129+
id: "1",
130+
text: "Test message",
131+
images: [],
132+
},
133+
]
134+
135+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
136+
137+
// Enter edit mode
138+
const messageElement = screen.getByTestId("mention").parentElement
139+
fireEvent.click(messageElement!)
140+
141+
// Edit the text
142+
const textarea = screen.getByRole("textbox")
143+
fireEvent.change(textarea, { target: { value: "Updated message" } })
144+
145+
// Cancel by pressing Escape
146+
fireEvent.keyDown(textarea, { key: "Escape" })
147+
148+
// Should not call onUpdate and should exit edit mode
149+
expect(mockOnUpdate).not.toHaveBeenCalled()
150+
expect(screen.queryByRole("textbox")).not.toBeInTheDocument()
151+
})
152+
153+
it("renders thumbnails for messages with images", () => {
154+
const queue: QueuedMessage[] = [
155+
{
156+
id: "1",
157+
text: "Message with images",
158+
images: ["image1.png", "image2.png"],
159+
},
160+
]
161+
162+
render(<QueuedMessages queue={queue} onRemove={mockOnRemove} onUpdate={mockOnUpdate} />)
163+
164+
// Check that images are rendered (the actual Thumbnails component renders img elements)
165+
const images = screen.getAllByRole("img")
166+
expect(images).toHaveLength(2)
167+
expect(images[0]).toHaveAttribute("src", "image1.png")
168+
expect(images[1]).toHaveAttribute("src", "image2.png")
169+
})
170+
})

0 commit comments

Comments
 (0)