diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index bc9c36a6c2..76a5c797fd 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -723,19 +723,42 @@ const ChatViewComponent: React.ForwardRefRenderFunction 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 ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +// Mock the Mention component +vi.mock("../Mention", () => ({ + Mention: ({ text }: { text: string }) => {text}, +})) + +// Mock the Thumbnails component +vi.mock("../common/Thumbnails", () => ({ + default: ({ images }: { images: string[] }) =>
{images.length} images
, +})) + +// Mock the Button component +vi.mock("@src/components/ui", () => ({ + Button: ({ children, onClick, ...props }: any) => ( + + ), +})) + +describe("QueuedMessages", () => { + const mockOnRemove = vi.fn() + const mockOnUpdate = vi.fn() + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("renders nothing when queue is empty", () => { + const { container } = render() + 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() + + 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() + + 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() + + 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() + + // 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() + + // 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() + + // 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") + }) +})