diff --git a/webview-ui/src/components/chat/__tests__/QueuedMessages.spec.tsx b/webview-ui/src/components/chat/__tests__/QueuedMessages.spec.tsx new file mode 100644 index 0000000000..c2d18e8cef --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/QueuedMessages.spec.tsx @@ -0,0 +1,230 @@ +// npx vitest run src/components/chat/__tests__/QueuedMessages.spec.tsx + +import React from "react" +import { render, screen, fireEvent } from "@/utils/test-utils" +import QueuedMessages from "../QueuedMessages" +import { QueuedMessage } from "@roo-code/types" + +// Mock the Thumbnails component +vi.mock("../../common/Thumbnails", () => ({ + default: function MockThumbnails({ images, style }: { images: string[]; style?: React.CSSProperties }) { + return ( +
+ {images.map((img, idx) => ( + {`Thumbnail + ))} +
+ ) + }, +})) + +// Mock the Mention component +vi.mock("../Mention", () => ({ + Mention: function MockMention({ text }: { text: string }) { + return {text} + }, +})) + +describe("QueuedMessages", () => { + const mockOnRemove = vi.fn() + const mockOnUpdate = vi.fn() + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("should not render when queue is empty", () => { + const { container } = render() + expect(container.firstChild).toBeNull() + }) + + it("should render queued messages without images", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Test message 1", + images: [], + }, + { + id: "msg-2", + text: "Test message 2", + images: [], + }, + ] + + render() + + expect(screen.getByText("queuedMessages.title")).toBeInTheDocument() + expect(screen.getByText("Test message 1")).toBeInTheDocument() + expect(screen.getByText("Test message 2")).toBeInTheDocument() + expect(screen.queryByTestId("thumbnails")).not.toBeInTheDocument() + }) + + it("should render queued messages with images", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Message with images", + images: ["data:image/png;base64,image1", "data:image/png;base64,image2"], + }, + ] + + render() + + expect(screen.getByText("Message with images")).toBeInTheDocument() + expect(screen.getByTestId("thumbnails")).toBeInTheDocument() + expect(screen.getByTestId("thumbnail-0")).toHaveAttribute("src", "data:image/png;base64,image1") + expect(screen.getByTestId("thumbnail-1")).toHaveAttribute("src", "data:image/png;base64,image2") + }) + + it("should render multiple messages with mixed content (some with images, some without)", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Text only message", + images: [], + }, + { + id: "msg-2", + text: "Message with one image", + images: ["data:image/png;base64,singleimage"], + }, + { + id: "msg-3", + text: "Message with multiple images", + images: ["data:image/png;base64,img1", "data:image/png;base64,img2", "data:image/png;base64,img3"], + }, + ] + + render() + + // Check all messages are rendered + expect(screen.getByText("Text only message")).toBeInTheDocument() + expect(screen.getByText("Message with one image")).toBeInTheDocument() + expect(screen.getByText("Message with multiple images")).toBeInTheDocument() + + // Check thumbnails are rendered for messages with images + const thumbnailContainers = screen.getAllByTestId("thumbnails") + expect(thumbnailContainers).toHaveLength(2) // Only msg-2 and msg-3 have images + + // Verify the correct number of images in each container + const allThumbnails = screen.getAllByTestId(/^thumbnail-/) + expect(allThumbnails).toHaveLength(4) // 1 + 3 images total + }) + + it("should handle remove action", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Test message", + images: ["data:image/png;base64,testimage"], + }, + ] + + render() + + const removeButton = screen.getByRole("button") + fireEvent.click(removeButton) + + expect(mockOnRemove).toHaveBeenCalledWith(0) + }) + + it("should handle edit action", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Original text", + images: [], + }, + ] + + render() + + // Click on the message to edit + const messageText = screen.getByText("Original text") + fireEvent.click(messageText) + + // Find the textarea and change its value + const textarea = screen.getByPlaceholderText("chat:editMessage.placeholder") + fireEvent.change(textarea, { target: { value: "Updated text" } }) + fireEvent.blur(textarea) + + expect(mockOnUpdate).toHaveBeenCalledWith(0, "Updated text") + }) + + it("should preserve images when editing message text", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Message with image", + images: ["data:image/png;base64,preservedimage"], + }, + ] + + const { rerender } = render() + + // Verify image is initially rendered + expect(screen.getByTestId("thumbnails")).toBeInTheDocument() + expect(screen.getByTestId("thumbnail-0")).toHaveAttribute("src", "data:image/png;base64,preservedimage") + + // Click to edit + const messageText = screen.getByText("Message with image") + fireEvent.click(messageText) + + // Update text + const textarea = screen.getByPlaceholderText("chat:editMessage.placeholder") + fireEvent.change(textarea, { target: { value: "Updated message with image" } }) + fireEvent.blur(textarea) + + // Simulate the parent component updating the queue + const updatedQueue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Updated message with image", + images: ["data:image/png;base64,preservedimage"], + }, + ] + + rerender() + + // Verify image is still rendered after edit + expect(screen.getByTestId("thumbnails")).toBeInTheDocument() + expect(screen.getByTestId("thumbnail-0")).toHaveAttribute("src", "data:image/png;base64,preservedimage") + }) + + it("should handle messages with undefined or null images gracefully", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Message without images property", + images: undefined as any, // Testing edge case + }, + { + id: "msg-2", + text: "Message with null images", + images: null as any, // Testing edge case + }, + ] + + render() + + expect(screen.getByText("Message without images property")).toBeInTheDocument() + expect(screen.getByText("Message with null images")).toBeInTheDocument() + expect(screen.queryByTestId("thumbnails")).not.toBeInTheDocument() + }) + + it("should apply correct styling to thumbnails", () => { + const queue: QueuedMessage[] = [ + { + id: "msg-1", + text: "Message with styled images", + images: ["data:image/png;base64,styledimage"], + }, + ] + + render() + + const thumbnails = screen.getByTestId("thumbnails") + expect(thumbnails).toHaveStyle({ marginTop: "8px" }) + }) +})