import { act, fireEvent, render, screen } from "@testing-library/react"; import * as React from "react"; import { describe, expect, it, vi } from "vitest"; import type { ChatInputHandle } from "./ChatInput"; import { ChatInput } from "./ChatInput"; describe("ChatInput component (Story 178 AC1)", () => { it("renders a textarea with Send a message... placeholder", () => { render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); expect(textarea.tagName.toLowerCase()).toBe("textarea"); }); it("manages input state internally — typing updates value without calling onSubmit", async () => { const onSubmit = vi.fn(); render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); await act(async () => { fireEvent.change(textarea, { target: { value: "hello world" } }); }); expect((textarea as HTMLTextAreaElement).value).toBe("hello world"); expect(onSubmit).not.toHaveBeenCalled(); }); it("calls onSubmit with the input text on Enter key press", async () => { const onSubmit = vi.fn(); render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); await act(async () => { fireEvent.change(textarea, { target: { value: "test message" } }); }); await act(async () => { fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); }); expect(onSubmit).toHaveBeenCalledWith("test message"); }); it("clears input after submitting", async () => { render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); await act(async () => { fireEvent.change(textarea, { target: { value: "hello" } }); }); await act(async () => { fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false }); }); expect((textarea as HTMLTextAreaElement).value).toBe(""); }); it("does not submit on Shift+Enter", async () => { const onSubmit = vi.fn(); render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); await act(async () => { fireEvent.change(textarea, { target: { value: "multiline" } }); }); await act(async () => { fireEvent.keyDown(textarea, { key: "Enter", shiftKey: true }); }); expect(onSubmit).not.toHaveBeenCalled(); expect((textarea as HTMLTextAreaElement).value).toBe("multiline"); }); it("calls onCancel when stop button is clicked while loading with empty input", async () => { const onCancel = vi.fn(); render( , ); const stopButton = screen.getByRole("button", { name: "■" }); await act(async () => { fireEvent.click(stopButton); }); expect(onCancel).toHaveBeenCalled(); }); it("renders queued message indicators", () => { render( , ); const indicators = screen.getAllByTestId("queued-message-indicator"); expect(indicators).toHaveLength(2); expect(indicators[0]).toHaveTextContent("first message"); expect(indicators[1]).toHaveTextContent("second message"); }); it("calls onRemoveQueuedMessage when cancel button is clicked", async () => { const onRemove = vi.fn(); render( , ); const cancelBtn = screen.getByTitle("Cancel queued message"); await act(async () => { fireEvent.click(cancelBtn); }); expect(onRemove).toHaveBeenCalledWith("q1"); }); it("edit button restores queued message text to input and removes from queue", async () => { const onRemove = vi.fn(); render( , ); const editBtn = screen.getByTitle("Edit queued message"); await act(async () => { fireEvent.click(editBtn); }); const textarea = screen.getByPlaceholderText("Send a message..."); expect((textarea as HTMLTextAreaElement).value).toBe("edit me back"); expect(onRemove).toHaveBeenCalledWith("q1"); }); }); describe("ChatInput appendToInput (Bug 215 regression)", () => { it("appendToInput sets text into an empty input", async () => { const ref = React.createRef(); render( , ); await act(async () => { ref.current?.appendToInput("queued message"); }); const textarea = screen.getByPlaceholderText("Send a message..."); expect((textarea as HTMLTextAreaElement).value).toBe("queued message"); }); it("appendToInput appends to existing input content with a newline separator", async () => { const ref = React.createRef(); render( , ); const textarea = screen.getByPlaceholderText("Send a message..."); await act(async () => { fireEvent.change(textarea, { target: { value: "existing text" } }); }); await act(async () => { ref.current?.appendToInput("appended text"); }); expect((textarea as HTMLTextAreaElement).value).toBe( "existing text\nappended text", ); }); it("multiple queued messages joined with newlines are appended on cancel", async () => { const ref = React.createRef(); render( , ); await act(async () => { ref.current?.appendToInput("msg one\nmsg two"); }); const textarea = screen.getByPlaceholderText("Send a message..."); expect((textarea as HTMLTextAreaElement).value).toBe("msg one\nmsg two"); }); });